Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b04653f24 | ||
|
|
868015da25 | ||
|
|
7aeffa242e | ||
|
|
3136c0586e | ||
|
|
fb0fcbca7f | ||
|
|
0d449c037b | ||
|
|
4d98230694 | ||
|
|
3d36e501a1 | ||
|
|
b4eb9a0827 | ||
|
|
aaf0015be4 | ||
|
|
1e8e163bb1 | ||
|
|
f3f741e1eb | ||
|
|
44f52ca6cd | ||
|
|
2b6e04bca3 | ||
|
|
784d57a2bc | ||
|
|
f4fdadd3b0 | ||
|
|
1cc9a52461 | ||
|
|
cd52982752 | ||
|
|
4dd58b79e9 | ||
|
|
c5c94276c2 | ||
|
|
8e0b9b685c | ||
|
|
1792bed721 | ||
|
|
a502cbc06e | ||
|
|
ddda7b8c52 | ||
|
|
0f19ba3231 | ||
|
|
36486a3d73 | ||
|
|
02f48a7781 | ||
|
|
c77f4dd794 | ||
|
|
ca31a08182 | ||
|
|
33936195a1 | ||
|
|
67bafa02fe |
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -4929,7 +4929,7 @@ void HistoryWidget::startItemRevealAnimations() {
|
||||
1.,
|
||||
HistoryView::ListWidget::kItemRevealDuration,
|
||||
anim::easeOutCirc);
|
||||
if (item->out()) {
|
||||
if (item->out() || _history->peer->isSelf()) {
|
||||
controller()->rotateComplexGradientBackground();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ®ular = 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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -27,11 +27,7 @@ public:
|
||||
const std::optional<Data::WallPaper> ¶ms);
|
||||
~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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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() {
|
||||
|
||||
428
Telegram/SourceFiles/ui/chat/message_bubble.cpp
Normal file
428
Telegram/SourceFiles/ui/chat/message_bubble.cpp
Normal 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
|
||||
74
Telegram/SourceFiles/ui/chat/message_bubble.h
Normal file
74
Telegram/SourceFiles/ui/chat/message_bubble.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Submodule Telegram/lib_spellcheck updated: 68c9b78895...212d660cbb
Submodule Telegram/lib_ui updated: 6b320a99da...5938cb012f
@@ -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.
|
||||
|
||||
@@ -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 ^
|
||||
|
||||
Reference in New Issue
Block a user