mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
339 lines
9.1 KiB
C++
339 lines
9.1 KiB
C++
// This is the source code of AyuGram for Desktop.
|
|
//
|
|
// We do not and cannot prevent the use of our code,
|
|
// but be respectful and credit the original author.
|
|
//
|
|
// Copyright @Radolyn, 2025
|
|
#include "now_playing.h"
|
|
|
|
#include "info/profile/info_profile_music_button.h"
|
|
|
|
#include <QSvgRenderer>
|
|
|
|
#include "ayu/ayu_settings.h"
|
|
#include "ayu/ui/utils/color_utils.h"
|
|
#include "ayu/ui/utils/palette.h"
|
|
#include "data/data_document.h"
|
|
#include "data/data_document_media.h"
|
|
#include "data/data_file_origin.h"
|
|
#include "data/data_session.h"
|
|
#include "main/main_session.h"
|
|
#include "styles/palette.h"
|
|
#include "styles/style_info.h"
|
|
#include "ui/painter.h"
|
|
#include "ui/image/image.h"
|
|
#include "ui/widgets/labels.h"
|
|
#include "window/themes/window_theme.h"
|
|
|
|
class Image;
|
|
|
|
namespace Info::Profile {
|
|
|
|
namespace {
|
|
|
|
QRgb AdjustHsl(QRgb color, float luminance, float saturation = -1.0f) {
|
|
auto hsl = Ayu::Ui::ColorUtils::colorToHSL(color);
|
|
|
|
if (saturation > 0.0f) {
|
|
hsl[1] = std::min(hsl[1] * saturation, 1.0f);
|
|
}
|
|
|
|
hsl[2] = std::min(hsl[2] * luminance, 1.0f);
|
|
|
|
return Ayu::Ui::ColorUtils::HSLToRGB(hsl);
|
|
}
|
|
|
|
QRgb BlendARGB(QRgb color1, QRgb color2, float ratio) {
|
|
const auto inverseRatio = 1.0f - ratio;
|
|
const auto r = static_cast<int>(qRed(color1) * inverseRatio + qRed(color2) * ratio);
|
|
const auto g = static_cast<int>(qGreen(color1) * inverseRatio + qGreen(color2) * ratio);
|
|
const auto b = static_cast<int>(qBlue(color1) * inverseRatio + qBlue(color2) * ratio);
|
|
const auto a = static_cast<int>(qAlpha(color1) * inverseRatio + qAlpha(color2) * ratio);
|
|
return qRgba(r, g, b, a);
|
|
}
|
|
|
|
QColor GetNoCoverBgColor() {
|
|
return Window::Theme::IsNightMode() ? st::windowBgOver->c.darker(170) : st::windowBgOver->c;
|
|
}
|
|
|
|
struct Cover
|
|
{
|
|
QPixmap pixToDraw;
|
|
QPixmap pixToBg;
|
|
bool noCover;
|
|
};
|
|
|
|
QPixmap MakeNoCoverImage(const QSize &size) {
|
|
static QPixmap result;
|
|
static auto resultTheme = Window::Theme::Background()->id();
|
|
if (!result.isNull() && result.size() == size && resultTheme == Window::Theme::Background()->id()) {
|
|
return result;
|
|
}
|
|
resultTheme = Window::Theme::Background()->id();
|
|
|
|
auto image = QImage(size, QImage::Format_ARGB32);
|
|
{
|
|
auto p = Painter(&image);
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
|
|
const auto bgColor = Window::Theme::IsNightMode()
|
|
? st::windowBoldFg->c.darker()
|
|
: st::windowBoldFg->c.lighter();
|
|
image.fill(bgColor);
|
|
|
|
auto svgIcon = QSvgRenderer(u":/gui/icons/ayu/nocover.svg"_q);
|
|
p.setPen(st::windowBoldFg->p);
|
|
svgIcon.render(&p, QRect(0, 0, size.width(), size.height()));
|
|
}
|
|
const auto img = Image(std::move(image));
|
|
result = img.pix(size, Images::PrepareArgs{.options = Images::Option::RoundSmall});
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Cover GetCurrentCover(
|
|
const std::shared_ptr<Data::DocumentMedia> &dataMedia,
|
|
const QSize &size) {
|
|
if (!dataMedia) {
|
|
return {
|
|
.pixToDraw = MakeNoCoverImage(size),
|
|
.pixToBg = MakeNoCoverImage(size),
|
|
.noCover = true
|
|
};
|
|
}
|
|
|
|
auto cover = QPixmap();
|
|
const auto scaled = [&](not_null<Image*> image)
|
|
{
|
|
const auto aspectRatio = Qt::KeepAspectRatioByExpanding;
|
|
return image->size().scaled(size, aspectRatio);
|
|
};
|
|
const auto args = Images::PrepareArgs{
|
|
.options = Images::Option::RoundSmall,
|
|
.outer = size,
|
|
};
|
|
if (const auto normal = dataMedia->thumbnail()) {
|
|
return {
|
|
.pixToDraw = normal->pixNoCache(scaled(normal), args),
|
|
.pixToBg = normal->pixNoCache(),
|
|
.noCover = false
|
|
};
|
|
} /*else if (const auto blurred = dataMedia->thumbnailInline()) {
|
|
return {
|
|
.pix = blurred->pixSingle(scaled(blurred), args.blurred()),
|
|
.noCover = false
|
|
};
|
|
}*/
|
|
|
|
return {
|
|
.pixToDraw = MakeNoCoverImage(size),
|
|
.pixToBg = MakeNoCoverImage(size),
|
|
.noCover = true
|
|
};
|
|
}
|
|
|
|
QRgb ExtractColorFromCover(const QPixmap &cover) {
|
|
const auto palette = Ayu::Ui::Palette::from(cover).generate();
|
|
|
|
const auto *swatch = palette.darkVibrantSwatch();
|
|
if (!swatch) {
|
|
swatch = palette.mutedSwatch();
|
|
}
|
|
if (!swatch) {
|
|
swatch = palette.darkMutedSwatch();
|
|
}
|
|
if (!swatch) {
|
|
swatch = palette.dominantSwatch();
|
|
}
|
|
|
|
if (!swatch) {
|
|
return GetNoCoverBgColor().rgb();
|
|
}
|
|
|
|
auto extractedColor = swatch->rgb();
|
|
|
|
const auto whiteColor = qRgb(255, 255, 255);
|
|
const auto contrast = Ayu::Ui::ColorUtils::calculateContrast(whiteColor, extractedColor);
|
|
|
|
auto adjustedColor = extractedColor;
|
|
if (contrast > 15.0f) {
|
|
adjustedColor = AdjustHsl(extractedColor, 2.0f);
|
|
} else if (static_cast<int>(contrast) < 10) {
|
|
adjustedColor = AdjustHsl(extractedColor, 0.5f);
|
|
}
|
|
|
|
if (Ayu::Ui::ColorUtils::calculateContrast(whiteColor, adjustedColor) < 3.0f) {
|
|
adjustedColor = BlendARGB(adjustedColor, qRgb(0, 0, 0), 0.3f);
|
|
}
|
|
|
|
return adjustedColor;
|
|
}
|
|
|
|
AyuMusicButton::AyuMusicButton(
|
|
QWidget *parent,
|
|
MusicButtonData data,
|
|
Fn<void()> handler)
|
|
: RippleButton(parent, st::infoMusicButtonRipple)
|
|
, _performer(std::make_unique<Ui::FlatLabel>(
|
|
this,
|
|
data.performer,
|
|
st::infoMusicButtonPerformer))
|
|
, _title(std::make_unique<Ui::FlatLabel>(
|
|
this,
|
|
data.title,
|
|
st::infoMusicButtonTitle))
|
|
, _mediaView(data.mediaView) {
|
|
rpl::combine(
|
|
_title->naturalWidthValue(),
|
|
_performer->naturalWidthValue()
|
|
) | rpl::start_with_next([=]
|
|
{
|
|
resizeToWidth(widthNoMargins());
|
|
},
|
|
lifetime());
|
|
|
|
_title->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
_performer->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
|
|
if (_mediaView && _mediaView->owner()->isSongWithCover() && !_mediaView->thumbnail()) {
|
|
_mediaView->thumbnailWanted(Data::FileOrigin(data.msgId));
|
|
_mediaView->owner()->owner().session().downloaderTaskFinished(
|
|
) | rpl::take_while([=]
|
|
{
|
|
if (_mediaView->thumbnail()) {
|
|
makeCover();
|
|
}
|
|
return !_mediaView->thumbnail();
|
|
}) | rpl::start(lifetime());
|
|
} else {
|
|
makeCover();
|
|
}
|
|
|
|
setClickedCallback(std::move(handler));
|
|
}
|
|
|
|
AyuMusicButton::~AyuMusicButton() = default;
|
|
|
|
void AyuMusicButton::updateData(MusicButtonData data) {
|
|
_performer->setText(data.performer);
|
|
_title->setText(data.title);
|
|
_mediaView = data.mediaView;
|
|
|
|
if (_mediaView && _mediaView->owner()->isSongWithCover() && !_mediaView->thumbnail()) {
|
|
_mediaView->thumbnailWanted(Data::FileOrigin(data.msgId));
|
|
_mediaView->owner()->owner().session().downloaderTaskFinished(
|
|
) | rpl::take_while([=]
|
|
{
|
|
if (_mediaView->thumbnail()) {
|
|
makeCover();
|
|
}
|
|
return !_mediaView->thumbnail();
|
|
}) | rpl::start(lifetime());
|
|
} else {
|
|
makeCover();
|
|
}
|
|
|
|
resizeToWidth(widthNoMargins());
|
|
}
|
|
|
|
void AyuMusicButton::makeCover() {
|
|
const auto weak = base::make_weak(this);
|
|
crl::async([=, mediaView = _mediaView]
|
|
{
|
|
const auto &settings = AyuSettings::getInstance();
|
|
const auto &font = st::infoMusicButtonTitle.style.font;
|
|
const auto skip = st::normalFont->spacew / 2;
|
|
const auto size = font->height + skip + font->height;
|
|
|
|
const auto cover = GetCurrentCover(mediaView, QSize(size, size));
|
|
QColor bgColor;
|
|
if (cover.noCover || !settings.adaptiveCoverColor) {
|
|
bgColor = GetNoCoverBgColor();
|
|
} else {
|
|
bgColor = QColor::fromRgb(ExtractColorFromCover(cover.pixToBg));
|
|
}
|
|
|
|
const auto strong = weak.get();
|
|
if (!strong) {
|
|
return;
|
|
}
|
|
|
|
strong->_currentCover = {
|
|
.pix = cover.pixToDraw,
|
|
.bg = bgColor,
|
|
.noCover = cover.noCover
|
|
};
|
|
|
|
crl::on_main([=]
|
|
{
|
|
const auto strong2 = weak.get();
|
|
if (!strong2) {
|
|
return;
|
|
}
|
|
|
|
strong2->repaint();
|
|
strong2->_title->repaint();
|
|
strong2->_performer->repaint();
|
|
|
|
strong2->_onReady.fire({});
|
|
});
|
|
});
|
|
}
|
|
|
|
void AyuMusicButton::paintEvent(QPaintEvent *e) {
|
|
if (!_currentCover) {
|
|
return;
|
|
}
|
|
|
|
auto p = Painter(this);
|
|
|
|
const auto &font = st::infoMusicButtonTitle.style.font;
|
|
const auto skip = st::normalFont->spacew / 2;
|
|
const auto size = font->height + skip + font->height;
|
|
|
|
const auto cover = _currentCover.value();
|
|
p.fillRect(e->rect(), cover.bg);
|
|
if (cover.noCover) {
|
|
paintRipple(p, QPoint());
|
|
}
|
|
|
|
if (!cover.pix.isNull()) {
|
|
const auto &settings = AyuSettings::getInstance();
|
|
if (!cover.noCover && settings.adaptiveCoverColor) {
|
|
_title->setTextColorOverride(Qt::white);
|
|
_performer->setTextColorOverride(Qt::lightGray);
|
|
} else {
|
|
_title->setTextColorOverride(std::nullopt);
|
|
_performer->setTextColorOverride(std::nullopt);
|
|
}
|
|
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
const auto coverRect = QRect(st::infoMusicButtonPadding.left(), st::infoMusicButtonPadding.top(), size, size);
|
|
p.drawPixmap(coverRect.topLeft(), cover.pix);
|
|
} else {
|
|
_title->setTextColorOverride(std::nullopt);
|
|
_performer->setTextColorOverride(std::nullopt);
|
|
}
|
|
}
|
|
|
|
int AyuMusicButton::resizeGetHeight(int newWidth) {
|
|
const auto padding = st::infoMusicButtonPadding;
|
|
const auto &font = st::infoMusicButtonTitle.style.font;
|
|
|
|
const auto top = padding.top();
|
|
const auto skip = st::normalFont->spacew / 2;
|
|
|
|
const auto coverSize = font->height + skip + font->height;
|
|
|
|
const auto available = newWidth - padding.left() - padding.right() - coverSize;
|
|
_title->resizeToNaturalWidth(available);
|
|
_title->moveToLeft(st::infoMusicButtonPadding.left() + padding.left() + coverSize, top);
|
|
_performer->resizeToNaturalWidth(available);
|
|
_performer->moveToLeft(st::infoMusicButtonPadding.left() + padding.left() + coverSize, top + font->height + skip);
|
|
|
|
return padding.top() + font->height + skip + font->height + padding.bottom();
|
|
}
|
|
|
|
} // namespace Info::Profile
|