Files
AyuGramDesktop/Telegram/SourceFiles/info/media/info_media_grid_zoom.cpp
T
2026-07-02 17:49:07 +04:00

254 lines
6.3 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 "info/media/info_media_grid_zoom.h"
#include "info/media/info_media_list_widget.h"
#include "info/media/info_media_list_section.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "ui/ui_utility.h"
#include "styles/style_info.h"
#include "styles/style_layers.h"
#include "styles/style_overview.h"
namespace Info::Media {
namespace {
constexpr auto kStepMin = -2;
constexpr auto kStepMax = 3;
constexpr auto kPinchStep = 0.5;
constexpr auto kFadeDuration = crl::time(200);
} // namespace
ListZoom::ListZoom(not_null<ListWidget*> list)
: _list(list)
, _step(std::clamp(
Core::App().settings().mediaGridZoomStep(),
kStepMin,
kStepMax)) {
}
bool ListZoom::isZoomable() const {
switch (_list->_provider->type()) {
case Type::Photo:
case Type::Video:
case Type::PhotoVideo:
case Type::RoundFile:
return true;
default:
return false;
}
}
int ListZoom::minVideoGridSize() const {
return 2 * st::overviewVideoStatusMargin
+ 3 * st::overviewVideoStatusPadding.x()
+ st::overviewVideoPlay.width()
+ st::normalFont->width(u"00:00"_q);
}
int ListZoom::minGridSizeForStep(int step) const {
const auto base = st::infoMediaMinGridSize
+ step * st::infoMediaGridSizeStep;
switch (_list->_provider->type()) {
case Type::Video:
case Type::PhotoVideo:
case Type::RoundFile:
return std::max(base, minVideoGridSize());
default:
return base;
}
}
int ListZoom::minGridSize() const {
return minGridSizeForStep(_step);
}
int ListZoom::columnsForGridSize(int gridSize) const {
const auto outer = _list->width();
if (outer <= 0) {
return 1;
}
const auto skip = st::infoMediaSkip;
const auto left = st::infoMediaLeft;
return std::max(1, (outer - left * 2 + skip) / (gridSize + skip));
}
std::optional<int> ListZoom::nextZoomStep(int direction) const {
if (_list->_sections.empty() || !isZoomable()) {
return std::nullopt;
}
const auto currentColumns = columnsForGridSize(minGridSize());
for (auto step = _step + direction
; step >= kStepMin && step <= kStepMax
; step += direction) {
const auto columns = columnsForGridSize(minGridSizeForStep(step));
if ((direction > 0)
? (columns < currentColumns)
: (columns > currentColumns)) {
return step;
}
}
return std::nullopt;
}
QPoint ListZoom::defaultAnchor() const {
return QPoint(
_list->width() / 2,
(_list->_visibleTop + _list->_visibleBottom) / 2);
}
void ListZoom::zoomBy(int direction, QPoint anchor) {
if (const auto step = nextZoomStep(direction)) {
apply(*step, anchor);
}
}
void ListZoom::zoomIn() {
zoomBy(1, defaultAnchor());
}
void ListZoom::zoomOut() {
zoomBy(-1, defaultAnchor());
}
bool ListZoom::canZoomIn() const {
return nextZoomStep(1).has_value();
}
bool ListZoom::canZoomOut() const {
return nextZoomStep(-1).has_value();
}
QPixmap ListZoom::grabViewport(int height) {
const auto width = _list->width();
if (width <= 0 || height <= 0) {
return QPixmap();
}
return Ui::GrabWidget(
_list.get(),
QRect(0, _list->_visibleTop, width, height),
st::boxBg->c);
}
void ListZoom::apply(int step, QPoint anchor) {
step = std::clamp(step, kStepMin, kStepMax);
if (step == _step || _list->_sections.empty()) {
return;
}
const auto oldColumns = columnsForGridSize(minGridSize());
const auto newColumns = columnsForGridSize(minGridSizeForStep(step));
_step = step;
Core::App().settings().setMediaGridZoomStep(step);
Core::App().saveSettingsDelayed();
if (newColumns == oldColumns) {
return;
}
const auto fadeHeight = _list->_visibleBottom - _list->_visibleTop;
const auto anchorScreen = QPoint(
anchor.x(),
anchor.y() - _list->_visibleTop);
_fade.stop();
auto oldPix = grabViewport(fadeHeight);
_list->_scrollTopState = _list->countScrollState(anchor);
_list->resizeToWidth(_list->width());
_list->restoreScrollState();
auto newPix = grabViewport(fadeHeight);
if (!oldPix.isNull() && !newPix.isNull() && fadeHeight > 0) {
_oldPix = std::move(oldPix);
_newPix = std::move(newPix);
_anchorScreen = anchorScreen;
_fadeHeight = fadeHeight;
_scaleRatio = float64(oldColumns) / newColumns;
_fade.start([=] {
_list->update(
QRect(0, _list->_visibleTop, _list->width(), _fadeHeight));
}, 0., 1., kFadeDuration, anim::easeOutCirc);
}
_list->update();
}
bool ListZoom::handleNativeGesture(not_null<QNativeGestureEvent*> e) {
if (e->gestureType() == Qt::BeginNativeGesture) {
_pinchAccumulated = 0.;
return false;
} else if (e->gestureType() != Qt::ZoomNativeGesture) {
return false;
} else if (!isZoomable()) {
return false;
}
const auto anchor = e->pos();
_pinchAccumulated += e->value();
while (_pinchAccumulated >= kPinchStep) {
_pinchAccumulated -= kPinchStep;
zoomBy(1, anchor);
}
while (_pinchAccumulated <= -kPinchStep) {
_pinchAccumulated += kPinchStep;
zoomBy(-1, anchor);
}
return true;
}
bool ListZoom::processWheel(not_null<QWheelEvent*> e) {
if (!isZoomable()) {
return false;
}
const auto delta = e->angleDelta().y();
if (!delta) {
return false;
}
const auto position = e->position();
const auto anchor = QPoint(
qRound(position.x()),
_list->_visibleTop + qRound(position.y()));
zoomBy((delta > 0) ? 1 : -1, anchor);
return true;
}
bool ListZoom::paint(QPainter &p) {
if (_fade.animating() && !_oldPix.isNull() && !_newPix.isNull()) {
const auto progress = _fade.value(1.);
const auto top = _list->_visibleTop;
const auto width = _list->width();
const auto target = QRect(0, top, width, _fadeHeight);
const auto pivot = QPointF(
_anchorScreen.x(),
top + _anchorScreen.y());
p.setRenderHint(QPainter::SmoothPixmapTransform);
const auto layer = [&](
const QPixmap &pix,
float64 scale,
float64 opacity) {
if (opacity <= 0.) {
return;
}
p.save();
p.setOpacity(opacity);
p.translate(pivot);
p.scale(scale, scale);
p.translate(-pivot);
p.drawPixmap(target, pix);
p.restore();
};
const auto inv = 1. / _scaleRatio;
layer(_newPix, inv + (1. - inv) * progress, progress);
layer(_oldPix, 1. + (_scaleRatio - 1.) * progress, 1. - progress);
return true;
} else if (!_oldPix.isNull() || !_newPix.isNull()) {
_oldPix = QPixmap();
_newPix = QPixmap();
}
return false;
}
} // namespace Info::Media