Files
AyuGramDesktop/Telegram/SourceFiles/iv/markdown/iv_markdown_slideshow_chrome.cpp
T
2026-07-10 14:04:29 +04:00

260 lines
6.7 KiB
C++

/*
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 "iv/markdown/iv_markdown_slideshow_chrome.h"
#include "ui/image/image_prepare.h"
#include "styles/palette.h"
#include "styles/style_iv.h"
#include <algorithm>
#include <cmath>
#include <limits>
namespace Iv::Markdown {
namespace {
constexpr auto kInactiveDotOpacity = 0.5;
[[nodiscard]] QImage GenerateSlideshowDotsBackdrop(
QSize outer,
int fade,
QColor color) {
const auto ratio = style::DevicePixelRatio();
const auto size = outer * ratio;
auto result = QImage(size, QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(ratio);
result.fill(Qt::transparent);
const auto f = fade * float64(ratio);
if (f <= 0. || size.isEmpty()) {
return result;
}
const auto cy = size.height() / 2.;
const auto radius = cy - f;
const auto from = f + radius;
const auto till = size.width() - f - radius;
const auto peak = color.alphaF();
for (auto y = 0; y != size.height(); ++y) {
const auto line = reinterpret_cast<uint32*>(result.scanLine(y));
const auto py = y + 0.5;
for (auto x = 0; x != size.width(); ++x) {
const auto px = x + 0.5;
const auto sx = std::clamp(px, from, till);
const auto distance = std::hypot(px - sx, py - cy) - radius;
const auto ramp = std::clamp((f - distance) / (2. * f), 0., 1.);
line[x] = qPremultiply(qRgba(
color.red(),
color.green(),
color.blue(),
int(std::round(ramp * peak * 255.))));
}
}
return result;
}
} // namespace
int MediaHeightForWidth(
int width,
int aspectWidth,
int aspectHeight) {
aspectWidth = std::max(aspectWidth, 1);
aspectHeight = std::max(aspectHeight, 1);
return std::max(
int((int64(width) * aspectHeight + aspectWidth - 1) / aspectWidth),
1);
}
QPainterPath RoundedRectPath(QRect rect, int radius) {
auto path = QPainterPath();
path.addRoundedRect(QRectF(rect), radius, radius);
return path;
}
void PaintRoundButton(
Painter &p,
QRect rect,
const style::color &bg,
const style::icon &icon) {
if (rect.isEmpty()) {
return;
}
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(bg->c);
p.drawEllipse(rect);
icon.paintInCenter(p, rect);
}
QImage PrepareWithBlurredBackground(
QSize outer,
QSize inner,
QImage large,
QImage blurred) {
const auto ratio = style::DevicePixelRatio();
auto background = QImage(
outer * ratio,
QImage::Format_ARGB32_Premultiplied);
background.setDevicePixelRatio(ratio);
if (blurred.isNull()) {
background.fill(Qt::black);
if (large.isNull()) {
return background;
}
}
auto p = QPainter(&background);
if (!blurred.isNull()) {
auto cover = blurred.scaled(
outer * ratio,
Qt::KeepAspectRatioByExpanding,
Qt::SmoothTransformation);
if (cover.size() != outer * ratio) {
cover = cover.copy(QRect(
QPoint(
(cover.width() - outer.width() * ratio) / 2,
(cover.height() - outer.height() * ratio) / 2),
outer * ratio));
}
cover = Images::Blur(std::move(cover), true);
cover.setDevicePixelRatio(ratio);
p.drawImage(QPoint(), cover);
p.fillRect(QRect(QPoint(), outer), QColor(0, 0, 0, 48));
}
if (!large.isNull()) {
auto image = large.scaled(
inner * ratio,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
image.setDevicePixelRatio(ratio);
p.drawImage(
(outer.width() - inner.width()) / 2,
(outer.height() - inner.height()) / 2,
image);
}
return background;
}
int SlideshowFrameHeight(
int width,
int slideshowMinHeight,
gsl::span<const QSize> slideOriginalSizes) {
auto result = std::numeric_limits<int>::max();
for (const auto &original : slideOriginalSizes) {
result = std::min(
result,
MediaHeightForWidth(width, original.width(), original.height()));
}
if (result == std::numeric_limits<int>::max()) {
result = std::max(slideshowMinHeight, 1);
}
return std::max(result, std::max(slideshowMinHeight, 1));
}
SlideshowNavRects ComputeSlideshowNavRects(
QRect frame,
int frameHeight,
int navButtonSize,
int navButtonSkip) {
const auto availableWidth = std::max(
(frame.width() - 2 * navButtonSkip) / 2,
0);
const auto size = std::min({
navButtonSize,
std::max(frameHeight, 0),
availableWidth,
});
if (size <= 0) {
return {};
}
const auto top = frame.y() + std::max((frameHeight - size) / 2, 0);
return {
.previous = QRect(
frame.x() + navButtonSkip,
top,
size,
size),
.next = QRect(
frame.x() + frame.width() - navButtonSkip - size,
top,
size,
size),
};
}
SlideshowDotsGeometry ComputeSlideshowDots(
QRect media,
int count,
int active,
const style::MarkdownGroupedMedia &st) {
const auto dot = st.dotSize;
const auto advance = st.dotSkip;
if (count < 2 || media.isEmpty() || dot <= 0 || advance <= 0) {
return {};
}
const auto padding = (advance - dot) / 2;
const auto bgHeight = dot + 2 * padding;
const auto fade = bgHeight / 2;
const auto available = media.width() - 2 * st.navButtonSkip;
const auto visible = std::clamp(
(available - 2 * fade) / advance,
1,
count);
const auto first = (visible == count)
? 0
: std::clamp(active - visible / 2, 0, count - visible);
const auto bgWidth = visible * advance;
const auto left = media.x() + (media.width() - bgWidth) / 2;
const auto bottom = media.y() + media.height() - st.dotsBottomSkip;
const auto core = QRect(left, bottom - bgHeight, bgWidth, bgHeight);
return {
.core = core,
.outer = core.marginsAdded({ fade, fade, fade, fade }),
.first = first,
.visible = visible,
};
}
void PaintSlideshowDots(
Painter &p,
const SlideshowDotsGeometry &dots,
int active,
const style::MarkdownGroupedMedia &st,
SlideshowDotsBackdrop &backdrop) {
if (dots.visible <= 0 || dots.core.isEmpty()) {
return;
}
const auto ratio = style::DevicePixelRatio();
const auto fade = (dots.outer.height() - dots.core.height()) / 2;
auto color = ::st::radialBg->c;
color.setAlphaF(st.dotsBgOpacity);
if (backdrop.image.size() != dots.outer.size() * ratio
|| backdrop.color != color) {
backdrop.image = GenerateSlideshowDotsBackdrop(
dots.outer.size(),
fade,
color);
backdrop.color = color;
}
p.drawImage(dots.outer.topLeft(), backdrop.image);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(::st::radialFg);
const auto dot = st.dotSize;
const auto advance = st.dotSkip;
const auto cy = dots.core.y() + dots.core.height() / 2;
auto cx = dots.core.x() + advance / 2;
for (auto i = 0; i != dots.visible; ++i, cx += advance) {
p.setOpacity((dots.first + i == active)
? 1.
: kInactiveDotOpacity);
p.drawEllipse(cx - dot / 2, cy - dot / 2, dot, dot);
}
p.setOpacity(1.);
}
} // namespace Iv::Markdown