Colorize bubbles according to a custom chat theme.

This commit is contained in:
John Preston 2021-08-27 23:44:47 +03:00
parent 5de83ef30c
commit beff635e45
38 changed files with 479 additions and 165 deletions

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwidget.h" #include "mainwidget.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "ui/chat/chat_theme.h" #include "ui/chat/chat_theme.h"
#include "ui/chat/chat_style.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/image/image.h" #include "ui/image/image.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
@ -365,6 +366,7 @@ BackgroundPreviewBox::BackgroundPreviewBox(
const Data::WallPaper &paper) const Data::WallPaper &paper)
: SimpleElementDelegate(controller, [=] { update(); }) : SimpleElementDelegate(controller, [=] { update(); })
, _controller(controller) , _controller(controller)
, _chatStyle(std::make_unique<Ui::ChatStyle>())
, _text1(GenerateTextItem( , _text1(GenerateTextItem(
delegate(), delegate(),
_controller->session().data().history(PeerData::kServiceNotificationsId), _controller->session().data().history(PeerData::kServiceNotificationsId),
@ -378,6 +380,8 @@ BackgroundPreviewBox::BackgroundPreviewBox(
, _paper(paper) , _paper(paper)
, _media(_paper.document() ? _paper.document()->createMediaView() : nullptr) , _media(_paper.document() ? _paper.document()->createMediaView() : nullptr)
, _radial([=](crl::time now) { radialAnimationCallback(now); }) { , _radial([=](crl::time now) { radialAnimationCallback(now); }) {
_chatStyle->apply(controller->defaultChatTheme().get());
if (_media) { if (_media) {
_media->thumbnailWanted(_paper.fileOrigin()); _media->thumbnailWanted(_paper.fileOrigin());
} }
@ -590,14 +594,10 @@ QRect BackgroundPreviewBox::radialRect() const {
void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) { void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) {
const auto height1 = _text1->height(); const auto height1 = _text1->height();
const auto height2 = _text2->height(); const auto height2 = _text2->height();
const auto context = HistoryView::PaintContext{ const auto context = _controller->defaultChatTheme()->preparePaintContext(
.st = style::main_palette::get(), _chatStyle.get(),
.bubblesPattern = nullptr, // #TODO bubbles rect(),
.viewport = rect(), rect());
.clip = rect(),
.selection = TextSelection(),
.now = ms,
};
p.translate(0, textsTop()); p.translate(0, textsTop());
paintDate(p); paintDate(p);
_text1->draw(p, context); _text1->draw(p, context);

View file

@ -25,6 +25,7 @@ class SessionController;
namespace Ui { namespace Ui {
class Checkbox; class Checkbox;
class ChatStyle;
} // namespace Ui } // namespace Ui
class BackgroundPreviewBox class BackgroundPreviewBox
@ -71,6 +72,7 @@ private:
void checkBlurAnimationStart(); void checkBlurAnimationStart();
const not_null<Window::SessionController*> _controller; const not_null<Window::SessionController*> _controller;
std::unique_ptr<Ui::ChatStyle> _chatStyle;
AdminLog::OwnedItem _text1; AdminLog::OwnedItem _text1;
AdminLog::OwnedItem _text2; AdminLog::OwnedItem _text2;
Data::WallPaper _paper; Data::WallPaper _paper;

View file

@ -242,6 +242,14 @@ InnerWidget::InnerWidget(
st::historyAdminLogEmptyWidth st::historyAdminLogEmptyWidth
- st::historyAdminLogEmptyPadding.left() - st::historyAdminLogEmptyPadding.left()
- st::historyAdminLogEmptyPadding.left()) { - st::historyAdminLogEmptyPadding.left()) {
Window::ChatThemeValueFromPeer(
controller,
channel
) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> &&theme) {
_theme = std::move(theme);
controller->setChatStyleTheme(_theme);
}, lifetime());
setMouseTracking(true); setMouseTracking(true);
_scrollDateHideTimer.setCallback([=] { scrollDateHideByTimer(); }); _scrollDateHideTimer.setCallback([=] { scrollDateHideByTimer(); });
session().data().viewRepaintRequest( session().data().viewRepaintRequest(
@ -915,13 +923,12 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
if (from != end) { if (from != end) {
auto viewport = QRect(); // #TODO bubbles auto viewport = QRect(); // #TODO bubbles
auto top = itemTop(from->get()); auto top = itemTop(from->get());
auto context = HistoryView::PaintContext{ auto context = _controller->preparePaintContext({
.st = style::main_palette::get(), .theme = _theme.get(),
.bubblesPattern = nullptr, .visibleAreaTop = _visibleTop,
.viewport = viewport.translated(0, -top), .visibleAreaTopGlobal = mapToGlobal(QPoint(0, _visibleTop)).y(),
.clip = clip.translated(0, -top), .clip = clip,
.now = crl::now(), }).translated(0, -top);
};
p.translate(0, top); p.translate(0, top);
for (auto i = from; i != to; ++i) { for (auto i = from; i != to; ++i) {
const auto view = i->get(); const auto view = i->get();

View file

@ -58,6 +58,9 @@ public:
not_null<ChannelData*> channel); not_null<ChannelData*> channel);
[[nodiscard]] Main::Session &session() const; [[nodiscard]] Main::Session &session() const;
[[nodiscard]] not_null<Ui::ChatTheme*> theme() const {
return _theme.get();
}
[[nodiscard]] rpl::producer<> showSearchSignal() const; [[nodiscard]] rpl::producer<> showSearchSignal() const;
[[nodiscard]] rpl::producer<int> scrollToSignal() const; [[nodiscard]] rpl::producer<int> scrollToSignal() const;
@ -253,6 +256,7 @@ private:
MTP::Sender _api; MTP::Sender _api;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient; const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
std::shared_ptr<Ui::ChatTheme> _theme;
std::vector<OwnedItem> _items; std::vector<OwnedItem> _items;
std::set<uint64> _eventIds; std::set<uint64> _eventIds;

View file

@ -470,11 +470,8 @@ void Widget::paintEvent(QPaintEvent *e) {
//auto ms = crl::now(); //auto ms = crl::now();
//_historyDownShown.step(ms); //_historyDownShown.step(ms);
SectionWidget::PaintBackground( const auto clip = e->rect();
controller(), SectionWidget::PaintBackground(controller(), _inner->theme(), this, clip);
controller()->defaultChatTheme().get(), // #TODO themes
this,
e->rect());
} }
void Widget::onScroll() { void Widget::onScroll() {

View file

@ -163,6 +163,14 @@ HistoryInner::HistoryInner(
, _scrollDateHideTimer([this] { scrollDateHideByTimer(); }) { , _scrollDateHideTimer([this] { scrollDateHideByTimer(); }) {
Instance = this; Instance = this;
Window::ChatThemeValueFromPeer(
controller,
_peer
) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> &&theme) {
_theme = std::move(theme);
controller->setChatStyleTheme(_theme);
}, lifetime());
_touchSelectTimer.setSingleShot(true); _touchSelectTimer.setSingleShot(true);
connect(&_touchSelectTimer, SIGNAL(timeout()), this, SLOT(onTouchSelect())); connect(&_touchSelectTimer, SIGNAL(timeout()), this, SLOT(onTouchSelect()));
@ -617,7 +625,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto top = mtop + block->y() + view->y(); auto top = mtop + block->y() + view->y();
auto context = _controller->preparePaintContext({ auto context = _controller->preparePaintContext({
.theme = _controller->defaultChatTheme().get(), // #TODO themes .theme = _theme.get(),
.visibleAreaTop = _visibleAreaTop, .visibleAreaTop = _visibleAreaTop,
.visibleAreaTopGlobal = visibleAreaTopGlobal, .visibleAreaTopGlobal = visibleAreaTopGlobal,
.clip = clip, .clip = clip,
@ -666,7 +674,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto readTill = (HistoryItem*)nullptr; auto readTill = (HistoryItem*)nullptr;
auto top = htop + block->y() + view->y(); auto top = htop + block->y() + view->y();
auto context = _controller->preparePaintContext({ auto context = _controller->preparePaintContext({
.theme = _controller->defaultChatTheme().get(), // #TODO themes .theme = _theme.get(),
.visibleAreaTop = _visibleAreaTop, .visibleAreaTop = _visibleAreaTop,
.visibleAreaTopGlobal = visibleAreaTopGlobal, .visibleAreaTopGlobal = visibleAreaTopGlobal,
.visibleAreaWidth = width(), .visibleAreaWidth = width(),

View file

@ -34,6 +34,7 @@ class SessionController;
} // namespace Window } // namespace Window
namespace Ui { namespace Ui {
class ChatTheme;
class PopupMenu; class PopupMenu;
enum class ReportReason; enum class ReportReason;
class PathShiftGradient; class PathShiftGradient;
@ -55,7 +56,10 @@ public:
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<History*> history); not_null<History*> history);
Main::Session &session() const; [[nodiscard]] Main::Session &session() const;
[[nodiscard]] not_null<Ui::ChatTheme*> theme() const {
return _theme.get();
}
void messagesReceived(PeerData *peer, const QVector<MTPMessage> &messages); void messagesReceived(PeerData *peer, const QVector<MTPMessage> &messages);
void messagesReceivedDown(PeerData *peer, const QVector<MTPMessage> &messages); void messagesReceivedDown(PeerData *peer, const QVector<MTPMessage> &messages);
@ -344,6 +348,7 @@ private:
const not_null<Window::SessionController*> _controller; const not_null<Window::SessionController*> _controller;
const not_null<PeerData*> _peer; const not_null<PeerData*> _peer;
const not_null<History*> _history; const not_null<History*> _history;
std::shared_ptr<Ui::ChatTheme> _theme;
History *_migrated = nullptr; History *_migrated = nullptr;
int _contentWidth = 0; int _contentWidth = 0;

View file

@ -4942,7 +4942,7 @@ void HistoryWidget::startItemRevealAnimations() {
HistoryView::ListWidget::kItemRevealDuration, HistoryView::ListWidget::kItemRevealDuration,
anim::easeOutCirc); anim::easeOutCirc);
if (item->out() || _history->peer->isSelf()) { if (item->out() || _history->peer->isSelf()) {
controller()->defaultChatTheme()->rotateComplexGradientBackground(); // #TODO themes _list->theme()->rotateComplexGradientBackground();
} }
} }
} }
@ -6888,7 +6888,7 @@ void HistoryWidget::paintEvent(QPaintEvent *e) {
Window::SectionWidget::PaintBackground( Window::SectionWidget::PaintBackground(
controller(), controller(),
controller()->defaultChatTheme().get(), // #TODO themes _list ? _list->theme().get() : controller()->defaultChatTheme().get(),
this, this,
e->rect()); e->rect());

View file

@ -143,6 +143,11 @@ public:
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override; not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
void elementReplyTo(const FullMsgId &to) override; void elementReplyTo(const FullMsgId &to) override;
protected:
[[nodiscard]] not_null<Window::SessionController*> controller() const {
return _controller;
}
private: private:
const not_null<Window::SessionController*> _controller; const not_null<Window::SessionController*> _controller;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient; const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;

View file

@ -1446,7 +1446,7 @@ void ListWidget::startItemRevealAnimations() {
kItemRevealDuration, kItemRevealDuration,
anim::easeOutCirc); anim::easeOutCirc);
if (view->data()->out()) { if (view->data()->out()) {
controller()->defaultChatTheme()->rotateComplexGradientBackground(); // #TODO themes _delegate->listChatTheme()->rotateComplexGradientBackground();
} }
} }
} }
@ -1611,16 +1611,16 @@ void ListWidget::paintEvent(QPaintEvent *e) {
auto to = std::lower_bound(begin(_items), end(_items), clip.top() + clip.height(), [this](auto &elem, int bottom) { auto to = std::lower_bound(begin(_items), end(_items), clip.top() + clip.height(), [this](auto &elem, int bottom) {
return this->itemTop(elem) < bottom; return this->itemTop(elem) < bottom;
}); });
if (from != end(_items)) { if (from != end(_items)) {
auto viewport = QRect(); // #TODO bubbles auto viewport = QRect(); // #TODO bubbles
auto top = itemTop(from->get()); auto top = itemTop(from->get());
auto context = HistoryView::PaintContext{ auto context = controller()->preparePaintContext({
.st = style::main_palette::get(), .theme = _delegate->listChatTheme(),
.bubblesPattern = nullptr, .visibleAreaTop = _visibleTop,
.viewport = viewport.translated(0, -top), .visibleAreaTopGlobal = mapToGlobal(QPoint(0, _visibleTop)).y(),
.clip = clip.translated(0, -top), .clip = clip,
.now = crl::now(), }).translated(0, -top);
};
p.translate(0, top); p.translate(0, top);
for (auto i = from; i != to; ++i) { for (auto i = from; i != to; ++i) {
const auto view = *i; const auto view = *i;

View file

@ -21,6 +21,7 @@ class Session;
namespace Ui { namespace Ui {
class PopupMenu; class PopupMenu;
class ChatTheme;
} // namespace Ui } // namespace Ui
namespace Window { namespace Window {
@ -91,6 +92,7 @@ public:
const QString &command, const QString &command,
const FullMsgId &context) = 0; const FullMsgId &context) = 0;
virtual void listHandleViaClick(not_null<UserData*> bot) = 0; virtual void listHandleViaClick(not_null<UserData*> bot) = 0;
virtual not_null<Ui::ChatTheme*> listChatTheme() = 0;
}; };

View file

@ -559,6 +559,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
p, p,
Ui::ComplexBubble{ Ui::ComplexBubble{
.simple = Ui::SimpleBubble{ .simple = Ui::SimpleBubble{
.st = context.st,
.geometry = g, .geometry = g,
.pattern = context.bubblesPattern, .pattern = context.bubblesPattern,
.patternViewport = context.viewport, .patternViewport = context.viewport,

View file

@ -101,6 +101,14 @@ PinnedWidget::PinnedWidget(
QString(), QString(),
st::historyComposeButton)) st::historyComposeButton))
, _scrollDown(_scroll.get(), st::historyToDown) { , _scrollDown(_scroll.get(), st::historyToDown) {
Window::ChatThemeValueFromPeer(
controller,
history->peer
) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> &&theme) {
_theme = std::move(theme);
controller->setChatStyleTheme(_theme);
}, lifetime());
_topBar->setActiveChat( _topBar->setActiveChat(
TopBarWidget::ActiveChat{ TopBarWidget::ActiveChat{
.key = _history, .key = _history,
@ -457,11 +465,7 @@ void PinnedWidget::paintEvent(QPaintEvent *e) {
const auto aboveHeight = _topBar->height(); const auto aboveHeight = _topBar->height();
const auto bg = e->rect().intersected( const auto bg = e->rect().intersected(
QRect(0, aboveHeight, width(), height() - aboveHeight)); QRect(0, aboveHeight, width(), height() - aboveHeight));
SectionWidget::PaintBackground( SectionWidget::PaintBackground(controller(), _theme.get(), this, bg);
controller(),
controller()->defaultChatTheme().get(), // #TODO themes
this,
bg);
} }
void PinnedWidget::onScroll() { void PinnedWidget::onScroll() {
@ -654,6 +658,10 @@ void PinnedWidget::listSendBotCommand(
void PinnedWidget::listHandleViaClick(not_null<UserData*> bot) { void PinnedWidget::listHandleViaClick(not_null<UserData*> bot) {
} }
not_null<Ui::ChatTheme*> PinnedWidget::listChatTheme() {
return _theme.get();
}
void PinnedWidget::confirmDeleteSelected() { void PinnedWidget::confirmDeleteSelected() {
ConfirmDeleteSelectedItems(_inner); ConfirmDeleteSelectedItems(_inner);
} }

View file

@ -102,6 +102,7 @@ public:
const QString &command, const QString &command,
const FullMsgId &context) override; const FullMsgId &context) override;
void listHandleViaClick(not_null<UserData*> bot) override; void listHandleViaClick(not_null<UserData*> bot) override;
not_null<Ui::ChatTheme*> listChatTheme() override;
protected: protected:
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
@ -145,6 +146,7 @@ private:
void refreshClearButtonText(); void refreshClearButtonText();
const not_null<History*> _history; const not_null<History*> _history;
std::shared_ptr<Ui::ChatTheme> _theme;
PeerData *_migratedPeer = nullptr; PeerData *_migratedPeer = nullptr;
QPointer<ListWidget> _inner; QPointer<ListWidget> _inner;
object_ptr<TopBarWidget> _topBar; object_ptr<TopBarWidget> _topBar;

View file

@ -162,6 +162,14 @@ RepliesWidget::RepliesWidget(
, _scroll(std::make_unique<Ui::ScrollArea>(this, st::historyScroll, false)) , _scroll(std::make_unique<Ui::ScrollArea>(this, st::historyScroll, false))
, _scrollDown(_scroll.get(), st::historyToDown) , _scrollDown(_scroll.get(), st::historyToDown)
, _readRequestTimer([=] { sendReadTillRequest(); }) { , _readRequestTimer([=] { sendReadTillRequest(); }) {
Window::ChatThemeValueFromPeer(
controller,
history->peer
) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> &&theme) {
_theme = std::move(theme);
controller->setChatStyleTheme(_theme);
}, lifetime());
setupRoot(); setupRoot();
setupRootView(); setupRootView();
@ -1511,11 +1519,7 @@ void RepliesWidget::paintEvent(QPaintEvent *e) {
const auto aboveHeight = _topBar->height(); const auto aboveHeight = _topBar->height();
const auto bg = e->rect().intersected( const auto bg = e->rect().intersected(
QRect(0, aboveHeight, width(), height() - aboveHeight)); QRect(0, aboveHeight, width(), height() - aboveHeight));
SectionWidget::PaintBackground( SectionWidget::PaintBackground(controller(), _theme.get(), this, bg);
controller(),
controller()->defaultChatTheme().get(), // #TODO themes
this,
bg);
} }
void RepliesWidget::onScroll() { void RepliesWidget::onScroll() {
@ -1786,6 +1790,10 @@ void RepliesWidget::listHandleViaClick(not_null<UserData*> bot) {
_composeControls->setText({ '@' + bot->username + ' ' }); _composeControls->setText({ '@' + bot->username + ' ' });
} }
not_null<Ui::ChatTheme*> RepliesWidget::listChatTheme() {
return _theme.get();
}
void RepliesWidget::confirmDeleteSelected() { void RepliesWidget::confirmDeleteSelected() {
ConfirmDeleteSelectedItems(_inner); ConfirmDeleteSelectedItems(_inner);
} }

View file

@ -137,6 +137,7 @@ public:
const QString &command, const QString &command,
const FullMsgId &context) override; const FullMsgId &context) override;
void listHandleViaClick(not_null<UserData*> bot) override; void listHandleViaClick(not_null<UserData*> bot) override;
not_null<Ui::ChatTheme*> listChatTheme() override;
protected: protected:
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
@ -250,6 +251,7 @@ private:
const not_null<History*> _history; const not_null<History*> _history;
const MsgId _rootId = 0; const MsgId _rootId = 0;
std::shared_ptr<Ui::ChatTheme> _theme;
HistoryItem *_root = nullptr; HistoryItem *_root = nullptr;
std::shared_ptr<Data::RepliesList> _replies; std::shared_ptr<Data::RepliesList> _replies;
rpl::variable<bool> _areComments = false; rpl::variable<bool> _areComments = false;

View file

@ -42,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/file_utilities.h" #include "core/file_utilities.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_scheduled_messages.h" #include "data/data_scheduled_messages.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "storage/storage_media_prepare.h" #include "storage/storage_media_prepare.h"
@ -101,6 +100,14 @@ ScheduledWidget::ScheduledWidget(
ComposeControls::Mode::Scheduled, ComposeControls::Mode::Scheduled,
SendMenu::Type::Disabled)) SendMenu::Type::Disabled))
, _scrollDown(_scroll, st::historyToDown) { , _scrollDown(_scroll, st::historyToDown) {
Window::ChatThemeValueFromPeer(
controller,
history->peer
) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> &&theme) {
_theme = std::move(theme);
controller->setChatStyleTheme(_theme);
}, lifetime());
const auto state = Dialogs::EntryState{ const auto state = Dialogs::EntryState{
.key = _history, .key = _history,
.section = Dialogs::EntryState::Section::Scheduled, .section = Dialogs::EntryState::Section::Scheduled,
@ -996,11 +1003,8 @@ void ScheduledWidget::paintEvent(QPaintEvent *e) {
//auto ms = crl::now(); //auto ms = crl::now();
//_historyDownShown.step(ms); //_historyDownShown.step(ms);
SectionWidget::PaintBackground( const auto clip = e->rect();
controller(), SectionWidget::PaintBackground(controller(), _theme.get(), this, clip);
controller()->defaultChatTheme().get(), // #TODO themes
this,
e->rect());
} }
void ScheduledWidget::onScroll() { void ScheduledWidget::onScroll() {
@ -1204,6 +1208,10 @@ void ScheduledWidget::listHandleViaClick(not_null<UserData*> bot) {
_composeControls->setText({ '@' + bot->username + ' ' }); _composeControls->setText({ '@' + bot->username + ' ' });
} }
not_null<Ui::ChatTheme*> ScheduledWidget::listChatTheme() {
return _theme.get();
}
void ScheduledWidget::confirmSendNowSelected() { void ScheduledWidget::confirmSendNowSelected() {
ConfirmSendNowSelectedItems(_inner); ConfirmSendNowSelectedItems(_inner);
} }

View file

@ -118,6 +118,7 @@ public:
const QString &command, const QString &command,
const FullMsgId &context) override; const FullMsgId &context) override;
void listHandleViaClick(not_null<UserData*> bot) override; void listHandleViaClick(not_null<UserData*> bot) override;
not_null<Ui::ChatTheme*> listChatTheme() override;
protected: protected:
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
@ -206,6 +207,7 @@ private:
Api::SendOptions options); Api::SendOptions options);
const not_null<History*> _history; const not_null<History*> _history;
std::shared_ptr<Ui::ChatTheme> _theme;
object_ptr<Ui::ScrollArea> _scroll; object_ptr<Ui::ScrollArea> _scroll;
QPointer<ListWidget> _inner; QPointer<ListWidget> _inner;
object_ptr<TopBarWidget> _topBar; object_ptr<TopBarWidget> _topBar;

View file

@ -78,12 +78,12 @@ static_assert(kDisplaySkipped != kTimeUnknown);
return result; return result;
} }
const auto errors = std::fetestexcept(FE_ALL_EXCEPT); const auto errors = std::fetestexcept(FE_ALL_EXCEPT);
LOG(("Streaming Error: Got NAN in std::round(%1), fe: %2."
).arg(value
).arg(errors));
if (const auto result = std::round(value); !std::isnan(result)) { if (const auto result = std::round(value); !std::isnan(result)) {
return result; return result;
} }
LOG(("Streaming Error: Got second NAN in std::round(%1), fe: %2."
).arg(value
).arg(errors));
std::feclearexcept(FE_ALL_EXCEPT); std::feclearexcept(FE_ALL_EXCEPT);
if (const auto result = std::round(value); !std::isnan(result)) { if (const auto result = std::round(value); !std::isnan(result)) {
return result; return result;

View file

@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_instance.h" #include "calls/calls_instance.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "ui/chat/chat_theme.h" #include "ui/chat/chat_theme.h"
#include "ui/chat/chat_style.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/wrap/padding_wrap.h" #include "ui/wrap/padding_wrap.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
@ -633,7 +634,9 @@ rpl::producer<QString> CallsPeer2PeerPrivacyController::exceptionsDescription()
ForwardsPrivacyController::ForwardsPrivacyController( ForwardsPrivacyController::ForwardsPrivacyController(
not_null<Window::SessionController*> controller) not_null<Window::SessionController*> controller)
: SimpleElementDelegate(controller, [] {}) : SimpleElementDelegate(controller, [] {})
, _controller(controller) { , _controller(controller)
, _chatStyle(std::make_unique<Ui::ChatStyle>()) {
_chatStyle->apply(controller->defaultChatTheme().get());
} }
UserPrivacy::Key ForwardsPrivacyController::key() { UserPrivacy::Key ForwardsPrivacyController::key() {
@ -715,13 +718,11 @@ object_ptr<Ui::RpWidget> ForwardsPrivacyController::setupAboveWidget(
rect); rect);
Painter p(widget); Painter p(widget);
const auto context = HistoryView::PaintContext{ const auto theme = _controller->defaultChatTheme().get();
.st = style::main_palette::get(), const auto context = theme->preparePaintContext(
.bubblesPattern = nullptr, _chatStyle.get(),
.viewport = widget->rect(), widget->rect(),
.clip = widget->rect(), widget->rect());
.now = crl::now(),
};
p.translate(0, padding + view->marginBottom()); p.translate(0, padding + view->marginBottom());
view->draw(p, context); view->draw(p, context);

View file

@ -16,6 +16,10 @@ namespace Window {
class SessionController; class SessionController;
} // namespace Window } // namespace Window
namespace Ui {
class ChatStyle;
} // namespace Ui
namespace Settings { namespace Settings {
class BlockedBoxController : public PeerListController { class BlockedBoxController : public PeerListController {
@ -193,6 +197,7 @@ private:
Option value); Option value);
const not_null<Window::SessionController*> _controller; const not_null<Window::SessionController*> _controller;
const std::unique_ptr<Ui::ChatStyle> _chatStyle;
}; };

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "support/support_autocomplete.h" #include "support/support_autocomplete.h"
#include "ui/chat/chat_theme.h" #include "ui/chat/chat_theme.h"
#include "ui/chat/chat_style.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
@ -25,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_session_settings.h" #include "main/main_session_settings.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "window/window_session_controller.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_window.h" #include "styles/style_window.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
@ -502,9 +504,11 @@ ConfirmContactBox::ConfirmContactBox(
const Contact &data, const Contact &data,
Fn<void(Qt::KeyboardModifiers)> submit) Fn<void(Qt::KeyboardModifiers)> submit)
: SimpleElementDelegate(controller, [=] { update(); }) : SimpleElementDelegate(controller, [=] { update(); })
, _chatStyle(std::make_unique<Ui::ChatStyle>())
, _comment(GenerateCommentItem(this, history, data)) , _comment(GenerateCommentItem(this, history, data))
, _contact(GenerateContactItem(this, history, data)) , _contact(GenerateContactItem(this, history, data))
, _submit(submit) { , _submit(submit) {
_chatStyle->apply(controller->defaultChatTheme().get());
} }
void ConfirmContactBox::prepare() { void ConfirmContactBox::prepare() {
@ -565,13 +569,11 @@ void ConfirmContactBox::paintEvent(QPaintEvent *e) {
p.fillRect(e->rect(), st::boxBg); p.fillRect(e->rect(), st::boxBg);
const auto context = HistoryView::PaintContext{ const auto theme = controller()->defaultChatTheme().get();
.st = style::main_palette::get(), const auto context = theme->preparePaintContext(
.bubblesPattern = nullptr, // #TODO bubbles _chatStyle.get(),
.viewport = rect(), rect(),
.clip = rect(), rect());
.now = crl::now(),
};
p.translate(st::boxPadding.left(), 0); p.translate(st::boxPadding.left(), 0);
if (_comment) { if (_comment) {
_comment->draw(p, context); _comment->draw(p, context);

View file

@ -24,6 +24,7 @@ class SessionController;
namespace Ui { namespace Ui {
class ScrollArea; class ScrollArea;
class InputField; class InputField;
class ChatStyle;
} // namespace Ui } // namespace Ui
namespace Support { namespace Support {
@ -83,6 +84,7 @@ protected:
void keyPressEvent(QKeyEvent *e) override; void keyPressEvent(QKeyEvent *e) override;
private: private:
std::unique_ptr<Ui::ChatStyle> _chatStyle;
AdminLog::OwnedItem _comment; AdminLog::OwnedItem _comment;
AdminLog::OwnedItem _contact; AdminLog::OwnedItem _contact;
Fn<void(Qt::KeyboardModifiers)> _submit; Fn<void(Qt::KeyboardModifiers)> _submit;

View file

@ -18,19 +18,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui { namespace Ui {
namespace { namespace {
struct CornersPixmaps {
QPixmap p[4];
};
std::vector<CornersPixmaps> Corners; std::vector<CornersPixmaps> Corners;
base::flat_map<uint32, CornersPixmaps> CornersMap; base::flat_map<uint32, CornersPixmaps> CornersMap;
QImage CornersMaskLarge[4], CornersMaskSmall[4]; QImage CornersMaskLarge[4], CornersMaskSmall[4];
rpl::lifetime PaletteChangedLifetime; rpl::lifetime PaletteChangedLifetime;
void PrepareCorners(CachedRoundCorners index, int32 radius, const QBrush &brush, const style::color *shadow = nullptr, QImage *cors = nullptr) { [[nodiscard]] std::array<QImage, 4> PrepareCorners(int32 radius, const QBrush &brush, const style::color *shadow = nullptr) {
Expects(Corners.size() > index);
int32 r = radius * style::DevicePixelRatio(), s = st::msgShadow * style::DevicePixelRatio(); int32 r = radius * style::DevicePixelRatio(), s = st::msgShadow * style::DevicePixelRatio();
QImage rect(r * 3, r * 3 + (shadow ? s : 0), QImage::Format_ARGB32_Premultiplied), localCors[4]; QImage rect(r * 3, r * 3 + (shadow ? s : 0), QImage::Format_ARGB32_Premultiplied);
{ {
Painter p(&rect); Painter p(&rect);
PainterHighQualityEnabler hq(p); PainterHighQualityEnabler hq(p);
@ -46,27 +41,31 @@ void PrepareCorners(CachedRoundCorners index, int32 radius, const QBrush &brush,
p.setBrush(brush); p.setBrush(brush);
p.drawRoundedRect(0, 0, r * 3, r * 3, r, r); p.drawRoundedRect(0, 0, r * 3, r * 3, r, r);
} }
if (!cors) cors = localCors; auto result = std::array<QImage, 4>();
cors[0] = rect.copy(0, 0, r, r); result[0] = rect.copy(0, 0, r, r);
cors[1] = rect.copy(r * 2, 0, r, r); result[1] = rect.copy(r * 2, 0, r, r);
cors[2] = rect.copy(0, r * 2, r, r + (shadow ? s : 0)); result[2] = rect.copy(0, r * 2, r, r + (shadow ? s : 0));
cors[3] = rect.copy(r * 2, r * 2, r, r + (shadow ? s : 0)); result[3] = rect.copy(r * 2, r * 2, r, r + (shadow ? s : 0));
if (index != SmallMaskCorners && index != LargeMaskCorners) { return result;
for (int i = 0; i < 4; ++i) { }
Corners[index].p[i] = PixmapFromImage(std::move(cors[i]));
Corners[index].p[i].setDevicePixelRatio(style::DevicePixelRatio()); void PrepareCorners(CachedRoundCorners index, int32 radius, const QBrush &brush, const style::color *shadow = nullptr) {
} Expects(index < Corners.size());
auto images = PrepareCorners(radius, brush, shadow);
for (int i = 0; i < 4; ++i) {
Corners[index].p[i] = PixmapFromImage(std::move(images[i]));
Corners[index].p[i].setDevicePixelRatio(style::DevicePixelRatio());
} }
} }
void CreateMaskCorners() { void CreateMaskCorners() {
QImage mask[4]; auto mask = PrepareCorners(st::roundRadiusSmall, QColor(255, 255, 255), nullptr);
PrepareCorners(SmallMaskCorners, st::roundRadiusSmall, QColor(255, 255, 255), nullptr, mask);
for (int i = 0; i < 4; ++i) { for (int i = 0; i < 4; ++i) {
CornersMaskSmall[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied); CornersMaskSmall[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied);
CornersMaskSmall[i].setDevicePixelRatio(style::DevicePixelRatio()); CornersMaskSmall[i].setDevicePixelRatio(style::DevicePixelRatio());
} }
PrepareCorners(LargeMaskCorners, st::historyMessageRadius, QColor(255, 255, 255), nullptr, mask); mask = PrepareCorners(st::historyMessageRadius, QColor(255, 255, 255), nullptr);
for (int i = 0; i < 4; ++i) { for (int i = 0; i < 4; ++i) {
CornersMaskLarge[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied); CornersMaskLarge[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied);
CornersMaskLarge[i].setDevicePixelRatio(style::DevicePixelRatio()); CornersMaskLarge[i].setDevicePixelRatio(style::DevicePixelRatio());
@ -231,23 +230,40 @@ void FillRoundShadow(Painter &p, int32 x, int32 y, int32 w, int32 h, style::colo
} }
} }
void FillRoundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, ImageRoundRadius radius, RectParts parts) { CornersPixmaps PrepareCornerPixmaps(int32 radius, style::color bg, const style::color *sh) {
auto colorKey = ((uint32(bg->c.alpha()) & 0xFF) << 24) | ((uint32(bg->c.red()) & 0xFF) << 16) | ((uint32(bg->c.green()) & 0xFF) << 8) | ((uint32(bg->c.blue()) & 0xFF) << 24); auto images = PrepareCorners(radius, bg, sh);
auto i = CornersMap.find(colorKey); auto result = CornersPixmaps();
if (i == CornersMap.cend()) { for (int j = 0; j < 4; ++j) {
QImage images[4]; result.p[j] = PixmapFromImage(std::move(images[j]));
switch (radius) { result.p[j].setDevicePixelRatio(style::DevicePixelRatio());
case ImageRoundRadius::Small: PrepareCorners(SmallMaskCorners, st::roundRadiusSmall, bg, nullptr, images); break; }
case ImageRoundRadius::Large: PrepareCorners(LargeMaskCorners, st::historyMessageRadius, bg, nullptr, images); break; return result;
default: p.fillRect(x, y, w, h, bg); return; }
}
CornersPixmaps pixmaps; CornersPixmaps PrepareCornerPixmaps(ImageRoundRadius radius, style::color bg, const style::color *sh) {
for (int j = 0; j < 4; ++j) { switch (radius) {
pixmaps.p[j] = PixmapFromImage(std::move(images[j])); case ImageRoundRadius::Small:
pixmaps.p[j].setDevicePixelRatio(style::DevicePixelRatio()); return PrepareCornerPixmaps(st::roundRadiusSmall, bg, sh);
} case ImageRoundRadius::Large:
i = CornersMap.emplace(colorKey, pixmaps).first; return PrepareCornerPixmaps(st::historyMessageRadius, bg, sh);
}
Unexpected("Image round radius in PrepareCornerPixmaps.");
}
void FillRoundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, ImageRoundRadius radius, RectParts parts) {
if (radius == ImageRoundRadius::None) {
p.fillRect(x, y, w, h, bg);
return;
}
const auto colorKey = ((uint32(bg->c.alpha()) & 0xFF) << 24)
| ((uint32(bg->c.red()) & 0xFF) << 16)
| ((uint32(bg->c.green()) & 0xFF) << 8)
| ((uint32(bg->c.blue()) & 0xFF));
auto i = CornersMap.find(colorKey);
if (i == end(CornersMap)) {
i = CornersMap.emplace(
colorKey,
PrepareCornerPixmaps(radius, bg, nullptr)).first;
} }
FillRoundRect(p, x, y, w, h, bg, i->second, nullptr, parts); FillRoundRect(p, x, y, w, h, bg, i->second, nullptr, parts);
} }

View file

@ -15,10 +15,11 @@ enum class ImageRoundRadius;
namespace Ui { namespace Ui {
enum CachedRoundCorners : int { struct CornersPixmaps {
SmallMaskCorners = 0x00, // for images QPixmap p[4];
LargeMaskCorners, };
enum CachedRoundCorners : int {
BoxCorners, BoxCorners,
MenuCorners, MenuCorners,
BotKbOverCorners, BotKbOverCorners,
@ -69,6 +70,16 @@ inline void FillRoundRect(Painter &p, const QRect &rect, style::color bg, ImageR
return FillRoundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, radius, parts); return FillRoundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, radius, parts);
} }
[[nodiscard]] CornersPixmaps PrepareCornerPixmaps(
int32 radius,
style::color bg,
const style::color *sh);
[[nodiscard]] CornersPixmaps PrepareCornerPixmaps(
ImageRoundRadius radius,
style::color bg,
const style::color *sh);
void FillRoundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, const CornersPixmaps &corner, const style::color *shadow = nullptr, RectParts parts = RectPart::Full);
void StartCachedCorners(); void StartCachedCorners();
void FinishCachedCorners(); void FinishCachedCorners();

View file

@ -0,0 +1,125 @@
/*
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/chat_style.h"
#include "ui/chat/chat_theme.h"
#include "styles/style_chat.h"
namespace Ui {
ChatStyle::ChatStyle() {
finalize();
messageIcon(
&MessageStyle::tailLeft,
st::historyBubbleTailInLeft,
st::historyBubbleTailInLeftSelected,
st::historyBubbleTailOutLeft,
st::historyBubbleTailOutLeftSelected);
messageIcon(
&MessageStyle::tailRight,
st::historyBubbleTailInRight,
st::historyBubbleTailInRightSelected,
st::historyBubbleTailOutRight,
st::historyBubbleTailOutRightSelected);
messageColor(
&MessageStyle::msgBg,
msgInBg(),
msgInBgSelected(),
msgOutBg(),
msgOutBgSelected());
messageColor(
&MessageStyle::msgShadow,
msgInShadow(),
msgInShadowSelected(),
msgOutShadow(),
msgOutShadowSelected());
}
void ChatStyle::apply(not_null<ChatTheme*> theme) {
const auto themePalette = theme->palette();
assignPalette(themePalette
? themePalette
: style::main_palette::get().get());
if (themePalette) {
_defaultPaletteChangeLifetime.destroy();
} else {
style::PaletteChanged(
) | rpl::start_with_next([=] {
assignPalette(style::main_palette::get());
}, _defaultPaletteChangeLifetime);
}
}
void ChatStyle::assignPalette(not_null<const style::palette*> palette) {
*static_cast<style::palette*>(this) = *palette;
style::internal::resetIcons();
for (auto &style : _messageStyles) {
style.corners = {};
}
}
const MessageStyle &ChatStyle::messageStyle(bool outbg, bool selected) const {
auto &result = messageStyleRaw(outbg, selected);
if (result.corners.p[0].isNull()) {
result.corners = Ui::PrepareCornerPixmaps(
st::historyMessageRadius,
result.msgBg,
&result.msgShadow);
}
return result;
}
MessageStyle &ChatStyle::messageStyleRaw(bool outbg, bool selected) const {
return _messageStyles[(outbg ? 2 : 0) + (selected ? 1 : 0)];
}
void ChatStyle::icon(style::icon &my, const style::icon &original) {
my = original.withPalette(*this);
}
MessageStyle &ChatStyle::messageIn() {
return messageStyleRaw(false, false);
}
MessageStyle &ChatStyle::messageInSelected() {
return messageStyleRaw(false, true);
}
MessageStyle &ChatStyle::messageOut() {
return messageStyleRaw(true, false);
}
MessageStyle &ChatStyle::messageOutSelected() {
return messageStyleRaw(true, true);
}
void ChatStyle::messageIcon(
style::icon MessageStyle::*my,
const style::icon &originalIn,
const style::icon &originalInSelected,
const style::icon &originalOut,
const style::icon &originalOutSelected) {
icon(messageIn().*my, originalIn);
icon(messageInSelected().*my, originalInSelected);
icon(messageOut().*my, originalOut);
icon(messageOutSelected().*my, originalOutSelected);
}
void ChatStyle::messageColor(
style::color MessageStyle::*my,
const style::color &originalIn,
const style::color &originalInSelected,
const style::color &originalOut,
const style::color &originalOutSelected) {
messageIn().*my = originalIn;
messageInSelected().*my = originalInSelected;
messageOut().*my = originalOut;
messageOutSelected().*my = originalOutSelected;
}
} // namespace Ui

View file

@ -0,0 +1,65 @@
/*
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/cached_round_corners.h"
namespace Ui {
class ChatTheme;
struct MessageStyle {
CornersPixmaps corners;
style::icon tailLeft = { Qt::Uninitialized };
style::icon tailRight = { Qt::Uninitialized };
style::color msgBg;
style::color msgShadow;
};
class ChatStyle final : public style::palette {
public:
ChatStyle();
void apply(not_null<ChatTheme*> theme);
[[nodiscard]] const MessageStyle &messageStyle(
bool outbg,
bool selected) const;
private:
void assignPalette(not_null<const style::palette*> palette);
void icon(style::icon &my, const style::icon &original);
[[nodiscard]] MessageStyle &messageStyleRaw(
bool outbg,
bool selected) const;
[[nodiscard]] MessageStyle &messageIn();
[[nodiscard]] MessageStyle &messageInSelected();
[[nodiscard]] MessageStyle &messageOut();
[[nodiscard]] MessageStyle &messageOutSelected();
void messageIcon(
style::icon MessageStyle::*my,
const style::icon &originalIn,
const style::icon &originalInSelected,
const style::icon &originalOut,
const style::icon &originalOutSelected);
void messageColor(
style::color MessageStyle::*my,
const style::color &originalIn,
const style::color &originalInSelected,
const style::color &originalOut,
const style::color &originalOutSelected);
mutable std::array<MessageStyle, 4> _messageStyles;
rpl::lifetime _defaultPaletteChangeLifetime;
};
} // namespace Ui

View file

@ -30,6 +30,8 @@ constexpr auto kMaxSize = 2960;
[[nodiscard]] CacheBackgroundResult CacheBackground( [[nodiscard]] CacheBackgroundResult CacheBackground(
const CacheBackgroundRequest &request) { const CacheBackgroundRequest &request) {
Expects(!request.area.isEmpty());
const auto gradient = request.background.gradientForFill.isNull() const auto gradient = request.background.gradientForFill.isNull()
? QImage() ? QImage()
: (request.gradientRotationAdd != 0) : (request.gradientRotationAdd != 0)
@ -69,12 +71,14 @@ constexpr auto kMaxSize = 2960;
Qt::KeepAspectRatio, Qt::KeepAspectRatio,
Qt::SmoothTransformation) Qt::SmoothTransformation)
: request.background.preparedForTiled; : request.background.preparedForTiled;
const auto w = tiled.width() / style::DevicePixelRatio(); const auto w = tiled.width() / float(style::DevicePixelRatio());
const auto h = tiled.height() / style::DevicePixelRatio(); const auto h = tiled.height() / float(style::DevicePixelRatio());
const auto cx = int(std::ceil(request.area.width() / w)); const auto cx = int(std::ceil(request.area.width() / w));
const auto cy = int(std::ceil(request.area.height() / h)); const auto cy = int(std::ceil(request.area.height() / h));
const auto rows = cy; const auto rows = cy;
const auto cols = request.background.isPattern ? (((cx / 2) * 2) + 1) : cx; const auto cols = request.background.isPattern
? (((cx / 2) * 2) + 1)
: cx;
const auto xshift = request.background.isPattern const auto xshift = request.background.isPattern
? (request.area.width() - cols * w) / 2 ? (request.area.width() - cols * w) / 2
: 0; : 0;
@ -202,13 +206,14 @@ void ChatTheme::setBubblesBackground(QImage image) {
}); });
} }
if (!_bubblesBackgroundPattern) { if (!_bubblesBackgroundPattern) {
_bubblesBackgroundPattern = PrepareBubblePattern(); _bubblesBackgroundPattern = PrepareBubblePattern(palette());
} }
_bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap; _bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap;
_repaintBackgroundRequests.fire({}); _repaintBackgroundRequests.fire({});
} }
ChatPaintContext ChatTheme::preparePaintContext( ChatPaintContext ChatTheme::preparePaintContext(
not_null<const ChatStyle*> st,
QRect viewport, QRect viewport,
QRect clip) { QRect clip) {
_bubblesBackground.area = viewport.size(); _bubblesBackground.area = viewport.size();
@ -223,7 +228,7 @@ ChatPaintContext ChatTheme::preparePaintContext(
// _bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap; // _bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap;
//} //}
return { return {
.st = _palette ? _palette.get() : style::main_palette::get(), .st = st,
.bubblesPattern = _bubblesBackgroundPattern.get(), .bubblesPattern = _bubblesBackgroundPattern.get(),
.viewport = viewport, .viewport = viewport,
.clip = clip, .clip = clip,
@ -240,6 +245,7 @@ const BackgroundState &ChatTheme::backgroundState(QSize area) {
&& !background().gradientForFill.isNull()) { && !background().gradientForFill.isNull()) {
// We don't support direct painting of patterned gradients. // We don't support direct painting of patterned gradients.
// So we need to sync-generate cache image here. // So we need to sync-generate cache image here.
_willCacheForArea = area;
setCachedBackground(CacheBackground(currentCacheRequest(area))); setCachedBackground(CacheBackground(currentCacheRequest(area)));
_cacheBackgroundTimer->cancel(); _cacheBackgroundTimer->cancel();
} else if (_backgroundState.now.area != area) { } else if (_backgroundState.now.area != area) {

View file

@ -13,10 +13,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui { namespace Ui {
class ChatStyle;
struct BubblePattern; struct BubblePattern;
struct ChatPaintContext { struct ChatPaintContext {
not_null<const style::palette*> st; not_null<const ChatStyle*> st;
const BubblePattern *bubblesPattern = nullptr; const BubblePattern *bubblesPattern = nullptr;
QRect viewport; QRect viewport;
QRect clip; QRect clip;
@ -112,22 +113,23 @@ public:
ChatTheme(ChatThemeDescriptor &&descriptor); ChatTheme(ChatThemeDescriptor &&descriptor);
[[nodiscard]] uint64 key() const; [[nodiscard]] uint64 key() const;
[[nodiscard]] not_null<const style::palette*> palette() const { [[nodiscard]] const style::palette *palette() const {
return _palette.get(); return _palette.get();
} }
void setBackground(ChatThemeBackground &&background); void setBackground(ChatThemeBackground &&background);
void updateBackgroundImageFrom(ChatThemeBackground &&background); void updateBackgroundImageFrom(ChatThemeBackground &&background);
const ChatThemeBackground &background() const { [[nodiscard]] const ChatThemeBackground &background() const {
return _mutableBackground; return _mutableBackground;
} }
void setBubblesBackground(QImage image); void setBubblesBackground(QImage image);
const BubblePattern *bubblesBackgroundPattern() const { [[nodiscard]] const BubblePattern *bubblesBackgroundPattern() const {
return _bubblesBackgroundPattern.get(); return _bubblesBackgroundPattern.get();
} }
[[nodiscard]] ChatPaintContext preparePaintContext( [[nodiscard]] ChatPaintContext preparePaintContext(
not_null<const ChatStyle*> st,
QRect viewport, QRect viewport,
QRect clip); QRect clip);
[[nodiscard]] const BackgroundState &backgroundState(QSize area); [[nodiscard]] const BackgroundState &backgroundState(QSize area);

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/cached_round_corners.h" #include "ui/cached_round_corners.h"
#include "ui/image/image_prepare.h" #include "ui/image/image_prepare.h"
#include "ui/chat/chat_style.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
namespace Ui { namespace Ui {
@ -79,12 +80,10 @@ void PaintBubbleGeneric(
} }
void PaintPatternBubble(Painter &p, const SimpleBubble &args) { void PaintPatternBubble(Painter &p, const SimpleBubble &args) {
const auto opacity = st::msgOutBg->c.alphaF(); const auto opacity = args.st->msgOutBg()->c.alphaF();
const auto shadowOpacity = opacity * st::msgOutShadow->c.alphaF(); const auto shadowOpacity = opacity * args.st->msgOutShadow()->c.alphaF();
const auto pattern = args.pattern; const auto pattern = args.pattern;
const auto sh = (args.skip & RectPart::Bottom) const auto sh = !(args.skip & RectPart::Bottom);
? nullptr
: &st::msgOutShadow;
const auto &tail = (args.tailSide == RectPart::Right) const auto &tail = (args.tailSide == RectPart::Right)
? pattern->tailRight ? pattern->tailRight
: pattern->tailLeft; : pattern->tailLeft;
@ -221,30 +220,14 @@ void PaintPatternBubble(Painter &p, const SimpleBubble &args) {
} }
void PaintSolidBubble(Painter &p, const SimpleBubble &args) { void PaintSolidBubble(Painter &p, const SimpleBubble &args) {
const auto &bg = args.selected const auto &st = args.st->messageStyle(args.outbg, args.selected);
? (args.outbg ? st::msgOutBgSelected : st::msgInBgSelected) const auto &bg = st.msgBg;
: (args.outbg ? st::msgOutBg : st::msgInBg);
const auto sh = (args.skip & RectPart::Bottom) const auto sh = (args.skip & RectPart::Bottom)
? nullptr ? nullptr
: args.selected : &st.msgShadow;
? &(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) const auto &tail = (args.tailSide == RectPart::Right)
? (args.selected ? st.tailRight
? st::historyBubbleTailOutRightSelected : st.tailLeft;
: st::historyBubbleTailOutRight)
: args.selected
? (args.outbg
? st::historyBubbleTailOutLeftSelected
: st::historyBubbleTailInLeftSelected)
: (args.outbg
? st::historyBubbleTailOutLeft
: st::historyBubbleTailInLeft);
const auto tailShift = (args.tailSide == RectPart::Right) const auto tailShift = (args.tailSide == RectPart::Right)
? QPoint(0, tail.height()) ? QPoint(0, tail.height())
: QPoint(tail.width(), tail.height()); : QPoint(tail.width(), tail.height());
@ -253,7 +236,7 @@ void PaintSolidBubble(Painter &p, const SimpleBubble &args) {
}, [&](const QRect &rect) { }, [&](const QRect &rect) {
p.fillRect(rect, *sh); p.fillRect(rect, *sh);
}, [&](const QRect &rect, RectParts parts) { }, [&](const QRect &rect, RectParts parts) {
Ui::FillRoundRect(p, rect, bg, corners, sh, parts); Ui::FillRoundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, st.corners, sh, parts);
}, [&](const QPoint &bottomPosition) { }, [&](const QPoint &bottomPosition) {
tail.paint(p, bottomPosition - tailShift, args.outerWidth); tail.paint(p, bottomPosition - tailShift, args.outerWidth);
return tail.width(); return tail.width();
@ -262,7 +245,8 @@ void PaintSolidBubble(Painter &p, const SimpleBubble &args) {
} // namespace } // namespace
std::unique_ptr<BubblePattern> PrepareBubblePattern() { std::unique_ptr<BubblePattern> PrepareBubblePattern(
not_null<const style::palette*> st) {
auto result = std::make_unique<Ui::BubblePattern>(); auto result = std::make_unique<Ui::BubblePattern>();
result->corners = Images::CornersMask(st::historyMessageRadius); result->corners = Images::CornersMask(st::historyMessageRadius);
const auto addShadow = [&](QImage &bottomCorner) { const auto addShadow = [&](QImage &bottomCorner) {
@ -274,7 +258,7 @@ std::unique_ptr<BubblePattern> PrepareBubblePattern() {
result.fill(Qt::transparent); result.fill(Qt::transparent);
result.setDevicePixelRatio(bottomCorner.devicePixelRatio()); result.setDevicePixelRatio(bottomCorner.devicePixelRatio());
auto p = QPainter(&result); auto p = QPainter(&result);
p.setOpacity(st::msgInShadow->c.alphaF()); p.setOpacity(st->msgInShadow()->c.alphaF());
p.drawImage(0, st::msgShadow, bottomCorner); p.drawImage(0, st::msgShadow, bottomCorner);
p.setOpacity(1.); p.setOpacity(1.);
p.drawImage(0, 0, bottomCorner); p.drawImage(0, 0, bottomCorner);

View file

@ -13,6 +13,9 @@ class Painter;
namespace Ui { namespace Ui {
class ChatTheme;
class ChatStyle;
struct BubbleSelectionInterval { struct BubbleSelectionInterval {
int top = 0; int top = 0;
int height = 0; int height = 0;
@ -28,9 +31,11 @@ struct BubblePattern {
mutable QImage tailCache; mutable QImage tailCache;
}; };
[[nodiscard]] std::unique_ptr<BubblePattern> PrepareBubblePattern(); [[nodiscard]] std::unique_ptr<BubblePattern> PrepareBubblePattern(
not_null<const style::palette*> st);
struct SimpleBubble { struct SimpleBubble {
not_null<const ChatStyle*> st;
QRect geometry; QRect geometry;
const BubblePattern *pattern = nullptr; const BubblePattern *pattern = nullptr;
QRect patternViewport; QRect patternViewport;

View file

@ -61,22 +61,6 @@ namespace {
}); });
} }
[[nodiscard]] auto ChatThemeValueFromPeer(
not_null<SessionController*> controller,
not_null<PeerData*> peer)
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
return MaybeCloudThemeValueFromPeer(
peer
) | rpl::map([=](std::optional<Data::CloudTheme> theme)
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
if (!theme) {
return rpl::single(controller->defaultChatTheme());
}
return controller->cachedChatThemeValue(*theme);
}) | rpl::flatten_latest(
) | rpl::distinct_until_changed();
}
} // namespace } // namespace
AbstractSectionWidget::AbstractSectionWidget( AbstractSectionWidget::AbstractSectionWidget(
@ -298,4 +282,20 @@ rpl::producer<int> SectionWidget::desiredHeight() const {
SectionWidget::~SectionWidget() = default; SectionWidget::~SectionWidget() = default;
auto ChatThemeValueFromPeer(
not_null<SessionController*> controller,
not_null<PeerData*> peer)
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
return MaybeCloudThemeValueFromPeer(
peer
) | rpl::map([=](std::optional<Data::CloudTheme> theme)
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
if (!theme) {
return rpl::single(controller->defaultChatTheme());
}
return controller->cachedChatThemeValue(*theme);
}) | rpl::flatten_latest(
) | rpl::distinct_until_changed();
}
} // namespace Window } // namespace Window

View file

@ -203,4 +203,9 @@ private:
}; };
[[nodiscard]] auto ChatThemeValueFromPeer(
not_null<SessionController*> controller,
not_null<PeerData*> peer)
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>>;
} // namespace Window } // namespace Window

View file

@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/delayed_activation.h" #include "ui/delayed_activation.h"
#include "ui/chat/message_bubble.h" #include "ui/chat/message_bubble.h"
#include "ui/chat/chat_style.h"
#include "ui/chat/chat_theme.h" #include "ui/chat/chat_theme.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/toasts/common_toasts.h" #include "ui/toasts/common_toasts.h"
@ -79,6 +80,7 @@ constexpr auto kMaxChatEntryHistorySize = 50;
std::optional<QColor> accent) { std::optional<QColor> accent) {
return [=](style::palette &palette) { return [=](style::palette &palette) {
using namespace Theme; using namespace Theme;
palette.finalize();
if (dark) { if (dark) {
const auto &embedded = EmbeddedThemes(); const auto &embedded = EmbeddedThemes();
const auto i = ranges::find( const auto i = ranges::find(
@ -506,9 +508,13 @@ SessionController::SessionController(
_window->widget(), _window->widget(),
this)) this))
, _invitePeekTimer([=] { checkInvitePeek(); }) , _invitePeekTimer([=] { checkInvitePeek(); })
, _defaultChatTheme(std::make_shared<Ui::ChatTheme>()) { , _defaultChatTheme(std::make_shared<Ui::ChatTheme>())
, _chatStyle(std::make_unique<Ui::ChatStyle>()) {
init(); init();
_chatStyleTheme = _defaultChatTheme;
_chatStyle->apply(_defaultChatTheme.get());
pushDefaultChatBackground(); pushDefaultChatBackground();
Theme::Background()->updates( Theme::Background()->updates(
) | rpl::start_with_next([=](const Theme::BackgroundUpdate &update) { ) | rpl::start_with_next([=](const Theme::BackgroundUpdate &update) {
@ -1378,6 +1384,15 @@ auto SessionController::cachedChatThemeValue(
}) | rpl::take(1)); }) | rpl::take(1));
} }
void SessionController::setChatStyleTheme(
const std::shared_ptr<Ui::ChatTheme> &theme) {
if (_chatStyleTheme.lock() == theme) {
return;
}
_chatStyleTheme = theme;
_chatStyle->apply(theme.get());
}
void SessionController::pushDefaultChatBackground() { void SessionController::pushDefaultChatBackground() {
const auto background = Theme::Background(); const auto background = Theme::Background();
const auto &paper = background->paper(); const auto &paper = background->paper();
@ -1520,7 +1535,10 @@ HistoryView::PaintContext SessionController::preparePaintContext(
args.visibleAreaTop - visibleAreaTopLocal, args.visibleAreaTop - visibleAreaTopLocal,
args.visibleAreaWidth, args.visibleAreaWidth,
content()->height()); content()->height());
return args.theme->preparePaintContext(viewport, args.clip); return args.theme->preparePaintContext(
_chatStyle.get(),
viewport,
args.clip);
} }
SessionController::~SessionController() { SessionController::~SessionController() {

View file

@ -45,6 +45,7 @@ class FormController;
namespace Ui { namespace Ui {
class LayerWidget; class LayerWidget;
enum class ReportReason; enum class ReportReason;
class ChatStyle;
class ChatTheme; class ChatTheme;
struct ChatPaintContext; struct ChatPaintContext;
struct ChatThemeBackground; struct ChatThemeBackground;
@ -404,6 +405,7 @@ public:
[[nodiscard]] auto cachedChatThemeValue( [[nodiscard]] auto cachedChatThemeValue(
const Data::CloudTheme &data) const Data::CloudTheme &data)
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>>; -> rpl::producer<std::shared_ptr<Ui::ChatTheme>>;
void setChatStyleTheme(const std::shared_ptr<Ui::ChatTheme> &theme);
struct PaintContextArgs { struct PaintContextArgs {
not_null<Ui::ChatTheme*> theme; not_null<Ui::ChatTheme*> theme;
@ -484,6 +486,8 @@ private:
std::shared_ptr<Ui::ChatTheme> _defaultChatTheme; std::shared_ptr<Ui::ChatTheme> _defaultChatTheme;
base::flat_map<uint64, CachedTheme> _customChatThemes; base::flat_map<uint64, CachedTheme> _customChatThemes;
rpl::event_stream<std::shared_ptr<Ui::ChatTheme>> _cachedThemesStream; rpl::event_stream<std::shared_ptr<Ui::ChatTheme>> _cachedThemesStream;
std::unique_ptr<Ui::ChatStyle> _chatStyle;
std::weak_ptr<Ui::ChatTheme> _chatStyleTheme;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;

View file

@ -135,6 +135,8 @@ PRIVATE
ui/chat/attach/attach_single_file_preview.h ui/chat/attach/attach_single_file_preview.h
ui/chat/attach/attach_single_media_preview.cpp ui/chat/attach/attach_single_media_preview.cpp
ui/chat/attach/attach_single_media_preview.h ui/chat/attach/attach_single_media_preview.h
ui/chat/chat_style.cpp
ui/chat/chat_style.h
ui/chat/chat_theme.cpp ui/chat/chat_theme.cpp
ui/chat/chat_theme.h ui/chat/chat_theme.h
ui/chat/group_call_bar.cpp ui/chat/group_call_bar.cpp

@ -1 +1 @@
Subproject commit 13117d03e5683af00f898a330aa319fd17efe8f7 Subproject commit 62158ed7955af3f7eabb1156075c1a41c4fff6b3

@ -1 +1 @@
Subproject commit 15ffd051d605be310051645412c54c2b9f87ff01 Subproject commit 3c95a9187194c07fda409f1ad1e142232bb82cbc