From 4a43dfd0911602f40379a6ea9c22d02711d44791 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 9 Dec 2025 02:19:48 +0300 Subject: [PATCH] Added profile color support to calls panel. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/calls/calls_panel.cpp | 37 ++- Telegram/SourceFiles/calls/calls_panel.h | 4 + .../calls/calls_panel_background.cpp | 225 ++++++++++++++++++ .../calls/calls_panel_background.h | 75 ++++++ 5 files changed, 341 insertions(+), 2 deletions(-) create mode 100644 Telegram/SourceFiles/calls/calls_panel_background.cpp create mode 100644 Telegram/SourceFiles/calls/calls_panel_background.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 19435203c9..5bf0f0a7b3 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -407,6 +407,8 @@ PRIVATE calls/calls_instance.h calls/calls_panel.cpp calls/calls_panel.h + calls/calls_panel_background.cpp + calls/calls_panel_background.h calls/calls_signal_bars.cpp calls/calls_signal_bars.h calls/calls_top_bar.cpp diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index aef7937de5..6293307ecd 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_panel.h" #include "boxes/peers/replace_boost_box.h" // CreateUserpicsWithMoreBadge +#include "calls/calls_panel_background.h" #include "data/data_photo.h" #include "data/data_session.h" #include "data/data_user.h" @@ -570,6 +571,13 @@ void Panel::reinitWithCall(Call *call) { _user = _call->user(); + _background = std::make_unique( + _user, + [=] { + updateTextColors(); + widget()->update(); + }); + _call->confereceSupportedValue( ) | rpl::start_with_next([=](bool supported) { _conferenceSupported = supported; @@ -763,6 +771,7 @@ void Panel::reinitWithCall(Call *call) { _name->setText(_user->name()); updateStatusText(_call->state()); + updateTextColors(); _answerHangupRedial->raise(); _decline->raise(); @@ -1239,9 +1248,21 @@ void Panel::paint(QRect clip) { if (!_incoming->widget()->isHidden()) { region = region.subtracted(QRegion(_incoming->widget()->geometry())); } - for (const auto &rect : region) { - p.fillRect(rect, st::callBgOpaque); + + if (_background) { + _background->paint( + p, + widget()->size(), + _bodyTop, + _bodySt->photoTop, + _bodySt->photoSize, + region); + } else { + for (const auto &rect : region) { + p.fillRect(rect, st::callBgOpaque); + } } + if (_incoming && _incoming->widget()->isHidden()) { _call->videoIncoming()->markFrameShown(); } @@ -1403,6 +1424,18 @@ void Panel::updateStatusText(State state) { updateStatusGeometry(); } +void Panel::updateTextColors() { + if (!_background) { + _name->setTextColorOverride(std::nullopt); + _status->setTextColorOverride(std::nullopt); + return; + } + _name->setTextColorOverride( + _background->textColorOverride(st::callName.textFg)); + _status->setTextColorOverride( + _background->textColorOverride(st::callStatus.textFg)); +} + void Panel::startDurationUpdateTimer(crl::time currentDuration) { auto msTillNextSecond = 1000 - (currentDuration % 1000); _updateDurationTimer.callOnce(msTillNextSecond + 5); diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h index bcdf373c42..66716a76be 100644 --- a/Telegram/SourceFiles/calls/calls_panel.h +++ b/Telegram/SourceFiles/calls/calls_panel.h @@ -64,6 +64,7 @@ class Window; class Userpic; class SignalBars; class VideoBubble; +class PanelBackground; struct DeviceSelection; struct ConferencePanelMigration; @@ -140,6 +141,7 @@ private: void stateChanged(State state); void showControls(); void updateStatusText(State state); + void updateTextColors(); void startDurationUpdateTimer(crl::time currentDuration); void setIncomingSize(QSize size); void refreshIncomingGeometry(); @@ -214,6 +216,8 @@ private: rpl::event_stream _startOutgoingRequests; + std::unique_ptr _background; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/calls/calls_panel_background.cpp b/Telegram/SourceFiles/calls/calls_panel_background.cpp new file mode 100644 index 0000000000..a98ab14105 --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_panel_background.cpp @@ -0,0 +1,225 @@ +/* +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 "calls/calls_panel_background.h" + +#include "apiwrap.h" +#include "api/api_peer_colors.h" +#include "data/data_changes.h" +#include "data/data_document.h" +#include "data/data_emoji_statuses.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "data/stickers/data_custom_emoji.h" +#include "main/main_session.h" +#include "ui/color_contrast.h" +#include "ui/top_background_gradient.h" +#include "styles/style_calls.h" + +namespace Calls { + +PanelBackground::PanelBackground( + not_null peer, + Fn updateCallback) +: _peer(peer) +, _updateCallback(std::move(updateCallback)) { + updateColors(); + updateEmojiId(); + + _peer->session().changes().peerFlagsValue( + _peer, + Data::PeerUpdate::Flag::ColorProfile + | Data::PeerUpdate::Flag::EmojiStatus + ) | rpl::start_with_next([=] { + updateColors(); + _brushSize = QSize(); + if (_updateCallback) { + _updateCallback(); + } + }, _lifetime); + + _peer->session().changes().peerFlagsValue( + _peer, + Data::PeerUpdate::Flag::BackgroundEmoji + | Data::PeerUpdate::Flag::EmojiStatus + ) | rpl::start_with_next([=] { + updateEmojiId(); + if (_updateCallback) { + _updateCallback(); + } + }, _lifetime); +} + +void PanelBackground::paint( + QPainter &p, + QSize widgetSize, + int bodyTop, + int photoTop, + int photoSize, + const QRegion ®ion) { + if (!_colors || _colors->bg.empty()) { + for (const auto &rect : region) { + p.fillRect(rect, st::callBgOpaque); + } + return; + } + + const auto &colors = _colors->bg; + if (colors.size() == 1) { + for (const auto &rect : region) { + p.fillRect(rect, colors.front()); + } + } else { + const auto center = QPoint( + widgetSize.width() / 2, + bodyTop + photoTop + photoSize / 2); + const auto radius = std::max( + std::hypot(center.x(), center.y()), + std::hypot(widgetSize.width() - center.x(), + widgetSize.height() - center.y())); + if (_brushSize != widgetSize) { + updateBrush(widgetSize, center, radius, colors); + } + for (const auto &rect : region) { + p.fillRect(rect, _brush); + } + } + + const auto emojiId = _currentEmojiId; + if (!emojiId) { + _emoji = nullptr; + _cachedImage = QImage(); + _cachedEmojiId = 0; + return; + } + + const auto userpicX = (widgetSize.width() - photoSize) / 2; + const auto userpicY = bodyTop + photoTop; + const auto padding = photoSize; + const auto patternRect = QRect( + userpicX - padding, + userpicY - padding / 2, + photoSize + padding * 2, + photoSize + padding); + + if (!_emoji + || _emoji->entityData() + != Data::SerializeCustomEmojiId(emojiId)) { + const auto document = _peer->owner().document(emojiId); + _emoji = document->owner().customEmojiManager().create( + document, + [=] { + _cachedImage = QImage(); + if (_updateCallback) { + _updateCallback(); + } + }, + Data::CustomEmojiSizeTag::Large); + _cachedImage = QImage(); + _cachedEmojiId = 0; + } + + if (_emoji && _emoji->ready()) { + if (_cachedImage.isNull() + || _cachedRect != patternRect + || _cachedEmojiId != emojiId) { + renderPattern(patternRect, emojiId); + } + p.drawImage(patternRect, _cachedImage); + } +} + +void PanelBackground::updateBrush( + QSize widgetSize, + QPoint center, + float64 radius, + const std::vector &colors) { + _brushSize = widgetSize; + auto gradient = QRadialGradient(center, radius); + const auto step = 1.0 / (colors.size() - 1); + for (auto i = 0; i < colors.size(); ++i) { + gradient.setColorAt(i * step, colors[colors.size() - 1 - i]); + } + _brush = QBrush(gradient); +} + +void PanelBackground::renderPattern(const QRect &rect, DocumentId emojiId) { + const auto ratio = style::DevicePixelRatio(); + _cachedImage = QImage( + rect.size() * ratio, + QImage::Format_ARGB32_Premultiplied); + _cachedImage.setDevicePixelRatio(ratio); + _cachedImage.fill(Qt::transparent); + + auto painter = QPainter(&_cachedImage); + const auto patternColor = QColor(0, 0, 0, int(0.6 * 255)); + const auto &points = Ui::PatternBgPoints(); + const auto localRect = QRect(QPoint(), rect.size()); + Ui::PaintBgPoints( + painter, + points, + _cache, + _emoji.get(), + patternColor, + localRect, + 1.); + + _cachedRect = rect; + _cachedEmojiId = emojiId; +} + +void PanelBackground::updateColors() { + const auto collectible = _peer->emojiStatusId().collectible; + if (collectible && collectible->centerColor.isValid()) { + _colors = Data::ColorProfileSet{ + .bg = { collectible->edgeColor, collectible->centerColor }, + }; + } else { + _colors = _peer->session().api().peerColors().colorProfileFor(_peer); + } +} + +void PanelBackground::updateEmojiId() { + const auto collectible = _peer->emojiStatusId().collectible; + _currentEmojiId = (collectible && collectible->patternDocumentId) + ? collectible->patternDocumentId + : _peer->profileBackgroundEmojiId(); +} + +std::optional PanelBackground::edgeColor() const { + const auto collectible = _peer->emojiStatusId().collectible; + if (collectible && collectible->edgeColor.isValid()) { + return collectible->edgeColor; + } + if (_colors && !_colors->bg.empty()) { + return _colors->bg.front(); + } + return std::nullopt; +} + +std::optional PanelBackground::textColorOverride( + const style::color &defaultColor) const { + const auto collectible = _peer->emojiStatusId().collectible; + if (collectible && collectible->textColor.isValid()) { + return collectible->textColor; + } + const auto edge = edgeColor(); + if (!edge) { + return std::nullopt; + } + constexpr auto kMinContrast = 5.5; + if (kMinContrast > Ui::CountContrast(defaultColor->c, *edge)) { + return st::groupCallMembersFg->c; + } + return std::nullopt; +} + +rpl::lifetime &PanelBackground::lifetime() { + return _lifetime; +} + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_panel_background.h b/Telegram/SourceFiles/calls/calls_panel_background.h new file mode 100644 index 0000000000..e4c00770ea --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_panel_background.h @@ -0,0 +1,75 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +#include "data/data_peer_colors.h" + +class DocumentData; +class PeerData; + +namespace Data { +class CustomEmoji; +} // namespace Data + +namespace Ui { +namespace Text { +class CustomEmoji; +} // namespace Text +} // namespace Ui + +namespace Calls { + +class PanelBackground final { +public: + explicit PanelBackground( + not_null peer, + Fn updateCallback); + + void paint( + QPainter &p, + QSize widgetSize, + int bodyTop, + int photoTop, + int photoSize, + const QRegion ®ion); + + [[nodiscard]] rpl::lifetime &lifetime(); + + [[nodiscard]] std::optional textColorOverride( + const style::color &defaultColor) const; + +private: + void updateBrush( + QSize widgetSize, + QPoint center, + float64 radius, + const std::vector &colors); + void renderPattern(const QRect &rect, DocumentId emojiId); + void updateColors(); + void updateEmojiId(); + [[nodiscard]] std::optional edgeColor() const; + + const not_null _peer; + const Fn _updateCallback; + + QBrush _brush; + QSize _brushSize; + + std::optional _colors; + + std::unique_ptr _emoji; + base::flat_map _cache; + QImage _cachedImage; + QRect _cachedRect; + DocumentId _cachedEmojiId = 0; + DocumentId _currentEmojiId = 0; + + rpl::lifetime _lifetime; + +}; + +} // namespace Calls