mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Added initial support gallery flag for inline bot results layout mode.
This commit is contained in:
@@ -44,6 +44,21 @@ using TextState = HistoryView::TextState;
|
||||
|
||||
constexpr auto kMaxInlineArea = 1280 * 720;
|
||||
|
||||
[[nodiscard]] QSize ScaleDown(int w, int h, int maxW, int maxH) {
|
||||
if (w * maxH > h * maxW) {
|
||||
if (maxH < h) {
|
||||
w = w * maxH / h;
|
||||
h = maxH;
|
||||
}
|
||||
} else {
|
||||
if (maxW < w) {
|
||||
h = h * maxW / w;
|
||||
w = maxW;
|
||||
}
|
||||
}
|
||||
return { w, h };
|
||||
}
|
||||
|
||||
[[nodiscard]] bool CanPlayInline(not_null<DocumentData*> document) {
|
||||
const auto dimensions = document->dimensions;
|
||||
return dimensions.width() * dimensions.height() <= kMaxInlineArea;
|
||||
@@ -1244,27 +1259,172 @@ void Contact::prepareThumbnail(int width, int height) const {
|
||||
&& (_thumb.height() == height * style::DevicePixelRatio()))) {
|
||||
return;
|
||||
}
|
||||
auto w = qMax(style::ConvertScale(thumb->width()), 1);
|
||||
auto h = qMax(style::ConvertScale(thumb->height()), 1);
|
||||
if (w * height > h * width) {
|
||||
if (height < h) {
|
||||
w = w * height / h;
|
||||
h = height;
|
||||
}
|
||||
} else {
|
||||
if (width < w) {
|
||||
h = h * width / w;
|
||||
w = width;
|
||||
}
|
||||
}
|
||||
const auto scaled = ScaleDown(
|
||||
qMax(style::ConvertScale(thumb->width()), 1),
|
||||
qMax(style::ConvertScale(thumb->height()), 1),
|
||||
width,
|
||||
height);
|
||||
_thumb = Image(base::duplicate(*thumb)).pixNoCache(
|
||||
QSize(w, h) * style::DevicePixelRatio(),
|
||||
scaled * style::DevicePixelRatio(),
|
||||
{
|
||||
.options = Images::Option::TransparentBackground,
|
||||
.outer = { width, height },
|
||||
});
|
||||
}
|
||||
|
||||
Thumbnail::Thumbnail(
|
||||
not_null<Context*> context,
|
||||
std::shared_ptr<Result> result)
|
||||
: ItemBase(context, std::move(result)) {
|
||||
}
|
||||
|
||||
void Thumbnail::initDimensions() {
|
||||
int w = 0, h = 0;
|
||||
if (const auto photo = getResultPhoto()) {
|
||||
w = photo->width();
|
||||
h = photo->height();
|
||||
} else if (const auto document = getResultDocument()) {
|
||||
w = document->dimensions.width();
|
||||
h = document->dimensions.height();
|
||||
}
|
||||
if (w <= 0 || h <= 0) {
|
||||
w = h = 1;
|
||||
}
|
||||
w = w * st::inlineMediaHeight / h;
|
||||
_maxw = qMax(w, int32(st::inlineResultsMinWidth));
|
||||
_minh = st::inlineMediaHeight + st::inlineResultsSkip;
|
||||
}
|
||||
|
||||
QSize Thumbnail::countFrameSize() const {
|
||||
int w = 0, h = 0;
|
||||
if (const auto photo = getResultPhoto()) {
|
||||
w = photo->width();
|
||||
h = photo->height();
|
||||
} else if (const auto document = getResultDocument()) {
|
||||
w = document->dimensions.width();
|
||||
h = document->dimensions.height();
|
||||
}
|
||||
if (w <= 0 || h <= 0) {
|
||||
return { _width, st::inlineMediaHeight };
|
||||
}
|
||||
// Aspect-fill: scale so the smaller dimension covers the cell.
|
||||
const auto targetHeight = st::inlineMediaHeight;
|
||||
if (w * targetHeight > h * _width) {
|
||||
w = w * targetHeight / h;
|
||||
h = targetHeight;
|
||||
} else {
|
||||
h = h * _width / w;
|
||||
w = _width;
|
||||
}
|
||||
return { qMax(w, 1), qMax(h, 1) };
|
||||
}
|
||||
|
||||
void Thumbnail::validateThumbnail(
|
||||
Image *image,
|
||||
QSize size,
|
||||
QSize frame,
|
||||
bool good) const {
|
||||
if (!image || (_thumbGood && !good)) {
|
||||
return;
|
||||
} else if ((_thumb.size() == size * style::DevicePixelRatio())
|
||||
&& (_thumbGood || !good)) {
|
||||
return;
|
||||
}
|
||||
_thumb = image->pixNoCache(
|
||||
frame * style::DevicePixelRatio(),
|
||||
{
|
||||
.options = (Images::Option::TransparentBackground
|
||||
| (good ? Images::Option() : Images::Option::Blur)),
|
||||
.outer = size,
|
||||
});
|
||||
_thumbGood = good;
|
||||
}
|
||||
|
||||
void Thumbnail::prepareThumbnail(QSize size, QSize frame) const {
|
||||
if (const auto photo = getResultPhoto()) {
|
||||
if (!_photoMedia) {
|
||||
_photoMedia = photo->createMediaView();
|
||||
_photoMedia->wanted(Data::PhotoSize::Thumbnail, fileOrigin());
|
||||
}
|
||||
using PhotoSize = Data::PhotoSize;
|
||||
validateThumbnail(
|
||||
_photoMedia->image(PhotoSize::Thumbnail),
|
||||
size,
|
||||
frame,
|
||||
true);
|
||||
validateThumbnail(
|
||||
_photoMedia->image(PhotoSize::Small),
|
||||
size,
|
||||
frame,
|
||||
false);
|
||||
validateThumbnail(
|
||||
_photoMedia->thumbnailInline(),
|
||||
size,
|
||||
frame,
|
||||
false);
|
||||
} else if (const auto document = getResultDocument()) {
|
||||
if (!_documentMedia) {
|
||||
_documentMedia = document->createMediaView();
|
||||
document->loadThumbnail(fileOrigin());
|
||||
}
|
||||
validateThumbnail(
|
||||
_documentMedia->thumbnail(),
|
||||
size,
|
||||
frame,
|
||||
true);
|
||||
validateThumbnail(
|
||||
_documentMedia->thumbnailInline(),
|
||||
size,
|
||||
frame,
|
||||
false);
|
||||
} else if (const auto thumb = getResultThumb(fileOrigin())) {
|
||||
if (_thumb.isNull()) {
|
||||
const auto scaled = ScaleDown(
|
||||
qMax(style::ConvertScale(thumb->width()), 1),
|
||||
qMax(style::ConvertScale(thumb->height()), 1),
|
||||
frame.width(),
|
||||
frame.height());
|
||||
_thumb = Image(base::duplicate(*thumb)).pixNoCache(
|
||||
scaled * style::DevicePixelRatio(),
|
||||
{
|
||||
.options = Images::Option::TransparentBackground,
|
||||
.outer = size,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Thumbnail::paint(
|
||||
Painter &p,
|
||||
const QRect &clip,
|
||||
const PaintContext *context) const {
|
||||
const auto height = st::inlineMediaHeight;
|
||||
const auto frame = countFrameSize();
|
||||
|
||||
QRect r(0, 0, _width, height);
|
||||
prepareThumbnail({ _width, height }, frame);
|
||||
if (_thumb.isNull()) {
|
||||
p.fillRect(r, st::overviewPhotoBg);
|
||||
} else {
|
||||
p.drawPixmap(r.topLeft(), _thumb);
|
||||
}
|
||||
}
|
||||
|
||||
TextState Thumbnail::getState(
|
||||
QPoint point,
|
||||
StateRequest request) const {
|
||||
if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
|
||||
return { nullptr, _send };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void Thumbnail::unloadHeavyPart() {
|
||||
_photoMedia = nullptr;
|
||||
_documentMedia = nullptr;
|
||||
ItemBase::unloadHeavyPart();
|
||||
}
|
||||
|
||||
Article::Article(
|
||||
not_null<Context*> context,
|
||||
std::shared_ptr<Result> result,
|
||||
@@ -1389,6 +1549,10 @@ TextState Article::getState(
|
||||
|
||||
void Article::prepareThumbnail(int width, int height) const {
|
||||
if (!hasResultThumb()) {
|
||||
prepareMediaThumbnail(width, height);
|
||||
if (!_thumb.isNull()) {
|
||||
return;
|
||||
}
|
||||
if ((_thumb.width() != width * style::DevicePixelRatio())
|
||||
|| (_thumb.height() != height * style::DevicePixelRatio())) {
|
||||
_thumb = getResultContactAvatar(width, height);
|
||||
@@ -1403,27 +1567,73 @@ void Article::prepareThumbnail(int width, int height) const {
|
||||
&& (_thumb.height() == height * style::DevicePixelRatio()))) {
|
||||
return;
|
||||
}
|
||||
auto w = qMax(style::ConvertScale(thumb->width()), 1);
|
||||
auto h = qMax(style::ConvertScale(thumb->height()), 1);
|
||||
if (w * height > h * width) {
|
||||
if (height < h) {
|
||||
w = w * height / h;
|
||||
h = height;
|
||||
}
|
||||
} else {
|
||||
if (width < w) {
|
||||
h = h * width / w;
|
||||
w = width;
|
||||
}
|
||||
}
|
||||
const auto scaled = ScaleDown(
|
||||
qMax(style::ConvertScale(thumb->width()), 1),
|
||||
qMax(style::ConvertScale(thumb->height()), 1),
|
||||
width,
|
||||
height);
|
||||
_thumb = Image(base::duplicate(*thumb)).pixNoCache(
|
||||
QSize(w, h) * style::DevicePixelRatio(),
|
||||
scaled * style::DevicePixelRatio(),
|
||||
{
|
||||
.options = Images::Option::TransparentBackground,
|
||||
.outer = { width, height },
|
||||
});
|
||||
}
|
||||
|
||||
void Article::prepareMediaThumbnail(int width, int height) const {
|
||||
auto thumbGood = false;
|
||||
const auto make = [&](Image *image, bool good) {
|
||||
if (!image || (thumbGood && !good)) {
|
||||
return;
|
||||
}
|
||||
if (!_thumb.isNull() && !good) {
|
||||
return;
|
||||
}
|
||||
const auto scaled = ScaleDown(
|
||||
qMax(style::ConvertScale(image->width()), 1),
|
||||
qMax(style::ConvertScale(image->height()), 1),
|
||||
width,
|
||||
height);
|
||||
_thumb = image->pixNoCache(
|
||||
scaled * style::DevicePixelRatio(),
|
||||
{
|
||||
.options = (Images::Option::TransparentBackground
|
||||
| (good
|
||||
? Images::Option()
|
||||
: Images::Option::Blur)),
|
||||
.outer = { width, height },
|
||||
});
|
||||
if (good) {
|
||||
thumbGood = true;
|
||||
}
|
||||
};
|
||||
if (const auto photo = getResultPhoto()) {
|
||||
if (!_photoMedia) {
|
||||
_photoMedia = photo->createMediaView();
|
||||
_photoMedia->wanted(Data::PhotoSize::Thumbnail, fileOrigin());
|
||||
}
|
||||
using PhotoSize = Data::PhotoSize;
|
||||
make(_photoMedia->image(PhotoSize::Thumbnail), true);
|
||||
make(_photoMedia->image(PhotoSize::Small), false);
|
||||
make(_photoMedia->thumbnailInline(), false);
|
||||
} else if (const auto document = getResultDocument()) {
|
||||
if (document->hasThumbnail()) {
|
||||
if (!_documentMedia) {
|
||||
_documentMedia = document->createMediaView();
|
||||
document->loadThumbnail(fileOrigin());
|
||||
}
|
||||
make(_documentMedia->thumbnail(), true);
|
||||
make(_documentMedia->thumbnailInline(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Article::unloadHeavyPart() {
|
||||
_photoMedia = nullptr;
|
||||
_documentMedia = nullptr;
|
||||
ItemBase::unloadHeavyPart();
|
||||
}
|
||||
|
||||
Game::Game(not_null<Context*> context, std::shared_ptr<Result> result)
|
||||
: ItemBase(context, std::move(result))
|
||||
, _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip)
|
||||
|
||||
@@ -364,6 +364,45 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class Thumbnail : public ItemBase {
|
||||
public:
|
||||
Thumbnail(not_null<Context*> context, std::shared_ptr<Result> result);
|
||||
|
||||
void initDimensions() override;
|
||||
|
||||
bool isFullLine() const override {
|
||||
return false;
|
||||
}
|
||||
bool hasRightSkip() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void paint(
|
||||
Painter &p,
|
||||
const QRect &clip,
|
||||
const PaintContext *context) const override;
|
||||
TextState getState(
|
||||
QPoint point,
|
||||
StateRequest request) const override;
|
||||
|
||||
void unloadHeavyPart() override;
|
||||
|
||||
private:
|
||||
QSize countFrameSize() const;
|
||||
void prepareThumbnail(QSize size, QSize frame) const;
|
||||
void validateThumbnail(
|
||||
Image *image,
|
||||
QSize size,
|
||||
QSize frame,
|
||||
bool good) const;
|
||||
|
||||
mutable QPixmap _thumb;
|
||||
mutable bool _thumbGood = false;
|
||||
mutable std::shared_ptr<Data::PhotoMedia> _photoMedia;
|
||||
mutable std::shared_ptr<Data::DocumentMedia> _documentMedia;
|
||||
|
||||
};
|
||||
|
||||
class Article : public ItemBase {
|
||||
public:
|
||||
Article(
|
||||
@@ -379,6 +418,8 @@ public:
|
||||
QPoint point,
|
||||
StateRequest request) const override;
|
||||
|
||||
void unloadHeavyPart() override;
|
||||
|
||||
private:
|
||||
ClickHandlerPtr _url, _link;
|
||||
|
||||
@@ -389,6 +430,10 @@ private:
|
||||
int32 _urlWidth;
|
||||
|
||||
void prepareThumbnail(int width, int height) const;
|
||||
void prepareMediaThumbnail(int width, int height) const;
|
||||
|
||||
mutable std::shared_ptr<Data::PhotoMedia> _photoMedia;
|
||||
mutable std::shared_ptr<Data::DocumentMedia> _documentMedia;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -93,9 +93,37 @@ void ItemBase::layoutChanged() {
|
||||
std::unique_ptr<ItemBase> ItemBase::createLayout(
|
||||
not_null<Context*> context,
|
||||
std::shared_ptr<Result> result,
|
||||
bool forceThumb) {
|
||||
bool forceThumb,
|
||||
std::optional<bool> gallery) {
|
||||
using Type = Result::Type;
|
||||
|
||||
if (gallery.has_value()) {
|
||||
if (!*gallery) {
|
||||
// Force list mode: render all types as Article.
|
||||
return std::make_unique<internal::Article>(
|
||||
context,
|
||||
std::move(result),
|
||||
forceThumb);
|
||||
} else {
|
||||
// Force gallery mode: render list types as Thumbnail.
|
||||
switch (result->_type) {
|
||||
case Type::Article:
|
||||
case Type::Geo:
|
||||
case Type::Venue:
|
||||
case Type::Video:
|
||||
case Type::Audio:
|
||||
case Type::File:
|
||||
case Type::Contact:
|
||||
case Type::Game:
|
||||
return std::make_unique<internal::Thumbnail>(
|
||||
context,
|
||||
std::move(result));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (result->_type) {
|
||||
case Type::Photo:
|
||||
return std::make_unique<internal::Photo>(context, std::move(result));
|
||||
|
||||
@@ -113,7 +113,8 @@ public:
|
||||
static std::unique_ptr<ItemBase> createLayout(
|
||||
not_null<Context*> context,
|
||||
std::shared_ptr<Result> result,
|
||||
bool forceThumb);
|
||||
bool forceThumb,
|
||||
std::optional<bool> gallery = std::nullopt);
|
||||
static std::unique_ptr<ItemBase> createLayoutGif(
|
||||
not_null<Context*> context,
|
||||
not_null<DocumentData*> document);
|
||||
|
||||
@@ -455,7 +455,8 @@ ItemBase *Inner::layoutPrepareInlineResult(std::shared_ptr<Result> result) {
|
||||
if (auto layout = ItemBase::createLayout(
|
||||
this,
|
||||
std::move(result),
|
||||
_inlineWithThumb)) {
|
||||
_inlineWithThumb,
|
||||
_gallery)) {
|
||||
it = _inlineLayouts.emplace(raw, std::move(layout)).first;
|
||||
it->second->initDimensions();
|
||||
} else {
|
||||
@@ -556,6 +557,8 @@ int Inner::refreshInlineRows(PeerData *queryPeer, UserData *bot, const CacheEntr
|
||||
|
||||
Assert(_inlineBot != 0);
|
||||
|
||||
_gallery = entry->gallery;
|
||||
|
||||
const auto count = int(entry->results.size());
|
||||
const auto from = validateExistingInlineRows(entry->results);
|
||||
auto added = 0;
|
||||
|
||||
@@ -58,6 +58,7 @@ struct CacheEntry {
|
||||
QString switchPmStartToken;
|
||||
QByteArray switchPmUrl;
|
||||
Results results;
|
||||
bool gallery = false;
|
||||
};
|
||||
|
||||
class Inner
|
||||
@@ -158,6 +159,7 @@ private:
|
||||
crl::time _lastUpdatedAt = 0;
|
||||
base::Timer _updateInlineItems;
|
||||
bool _inlineWithThumb = false;
|
||||
bool _gallery = false;
|
||||
|
||||
object_ptr<Ui::RoundButton> _switchPmButton = { nullptr };
|
||||
QString _switchPmStartToken;
|
||||
|
||||
@@ -397,6 +397,7 @@ void Widget::inlineResultsDone(const MTPmessages_BotResults &result) {
|
||||
entry->switchPmStartToken = QString();
|
||||
entry->switchPmUrl = switchWebView->data().vurl().v;
|
||||
}
|
||||
entry->gallery = d.is_gallery();
|
||||
|
||||
if (const auto count = v.size()) {
|
||||
entry->results.reserve(entry->results.size() + count);
|
||||
|
||||
Reference in New Issue
Block a user