mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 13:47:05 +02:00
Implement weather area in stories.
This commit is contained in:
parent
54ce85f8e6
commit
5fdd4eba80
8 changed files with 369 additions and 34 deletions
|
@ -41,13 +41,10 @@ using UpdateFlag = StoryUpdate::Flag;
|
|||
return {
|
||||
.geometry = { corner / 100., size / 100. },
|
||||
.rotation = data.vrotation().v,
|
||||
.radius = data.vradius().value_or_empty(),
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32 ParseMilliKelvin(double celcius) {
|
||||
return uint32(std::clamp(celcius + 273.15, 0., 1'000'000.) * 1000.);
|
||||
}
|
||||
|
||||
[[nodiscard]] TextWithEntities StripLinks(TextWithEntities text) {
|
||||
const auto link = [&](const EntityInText &entity) {
|
||||
return (entity.type() == EntityType::CustomUrl)
|
||||
|
@ -176,8 +173,11 @@ using UpdateFlag = StoryUpdate::Flag;
|
|||
result.emplace(WeatherArea{
|
||||
.area = ParseArea(data.vcoordinates()),
|
||||
.emoji = qs(data.vemoji()),
|
||||
.color = Ui::ColorFromSerialized(data.vcolor().v),
|
||||
.millicelcius = int(data.vtemperature_c().v * 1000.),
|
||||
.color = Ui::Color32FromSerialized(data.vcolor().v),
|
||||
.millicelsius = int(1000. * std::clamp(
|
||||
data.vtemperature_c().v,
|
||||
-274.,
|
||||
1'000'000.)),
|
||||
});
|
||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||
|
@ -721,6 +721,10 @@ const std::vector<UrlArea> &Story::urlAreas() const {
|
|||
return _urlAreas;
|
||||
}
|
||||
|
||||
const std::vector<WeatherArea> &Story::weatherAreas() const {
|
||||
return _weatherAreas;
|
||||
}
|
||||
|
||||
void Story::applyChanges(
|
||||
StoryMedia media,
|
||||
const MTPDstoryItem &data,
|
||||
|
@ -825,6 +829,7 @@ void Story::applyFields(
|
|||
auto suggestedReactions = std::vector<SuggestedReaction>();
|
||||
auto channelPosts = std::vector<ChannelPost>();
|
||||
auto urlAreas = std::vector<UrlArea>();
|
||||
auto weatherAreas = std::vector<WeatherArea>();
|
||||
if (const auto areas = data.vmedia_areas()) {
|
||||
for (const auto &area : areas->v) {
|
||||
if (const auto location = ParseLocation(area)) {
|
||||
|
@ -840,6 +845,8 @@ void Story::applyFields(
|
|||
channelPosts.push_back(*post);
|
||||
} else if (auto url = ParseUrlArea(area)) {
|
||||
urlAreas.push_back(*url);
|
||||
} else if (auto weather = ParseWeatherArea(area)) {
|
||||
weatherAreas.push_back(*weather);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -853,6 +860,7 @@ void Story::applyFields(
|
|||
= (_suggestedReactions != suggestedReactions);
|
||||
const auto channelPostsChanged = (_channelPosts != channelPosts);
|
||||
const auto urlAreasChanged = (_urlAreas != urlAreas);
|
||||
const auto weatherAreasChanged = (_weatherAreas != weatherAreas);
|
||||
const auto reactionChanged = (_sentReactionId != reaction);
|
||||
|
||||
_out = out;
|
||||
|
@ -881,6 +889,9 @@ void Story::applyFields(
|
|||
if (urlAreasChanged) {
|
||||
_urlAreas = std::move(urlAreas);
|
||||
}
|
||||
if (weatherAreasChanged) {
|
||||
_weatherAreas = std::move(weatherAreas);
|
||||
}
|
||||
if (reactionChanged) {
|
||||
_sentReactionId = reaction;
|
||||
}
|
||||
|
@ -891,7 +902,8 @@ void Story::applyFields(
|
|||
|| mediaChanged
|
||||
|| locationsChanged
|
||||
|| channelPostsChanged
|
||||
|| urlAreasChanged;
|
||||
|| urlAreasChanged
|
||||
|| weatherAreasChanged;
|
||||
const auto reactionsChanged = reactionChanged
|
||||
|| suggestedReactionsChanged;
|
||||
if (!initial && (changed || reactionsChanged)) {
|
||||
|
|
|
@ -81,6 +81,7 @@ struct StoryViews {
|
|||
struct StoryArea {
|
||||
QRectF geometry;
|
||||
float64 rotation = 0;
|
||||
float64 radius = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
const StoryArea &,
|
||||
|
@ -135,7 +136,7 @@ struct WeatherArea {
|
|||
StoryArea area;
|
||||
QString emoji;
|
||||
QColor color;
|
||||
int millicelcius = 0;
|
||||
int millicelsius = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
const WeatherArea &,
|
||||
|
@ -219,6 +220,8 @@ public:
|
|||
-> const std::vector<ChannelPost> &;
|
||||
[[nodiscard]] auto urlAreas() const
|
||||
-> const std::vector<UrlArea> &;
|
||||
[[nodiscard]] auto weatherAreas() const
|
||||
-> const std::vector<WeatherArea> &;
|
||||
|
||||
void applyChanges(
|
||||
StoryMedia media,
|
||||
|
@ -270,6 +273,7 @@ private:
|
|||
std::vector<SuggestedReaction> _suggestedReactions;
|
||||
std::vector<ChannelPost> _channelPosts;
|
||||
std::vector<UrlArea> _urlAreas;
|
||||
std::vector<WeatherArea> _weatherAreas;
|
||||
StoryViews _views;
|
||||
StoryViews _channelReactions;
|
||||
const TimeId _date = 0;
|
||||
|
|
|
@ -536,8 +536,9 @@ void Controller::rebuildActiveAreas(const Layout &layout) const {
|
|||
int(base::SafeRound(general.width() * scale.width())),
|
||||
int(base::SafeRound(general.height() * scale.height()))
|
||||
).translated(origin);
|
||||
if (const auto reaction = area.reaction.get()) {
|
||||
reaction->setAreaGeometry(area.geometry);
|
||||
area.radius = scale.width() * area.radiusOriginal / 100.;
|
||||
if (const auto view = area.view.get()) {
|
||||
view->setAreaGeometry(area.geometry, area.radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1050,6 +1051,9 @@ void Controller::updateAreas(Data::Story *story) {
|
|||
const auto &urlAreas = story
|
||||
? story->urlAreas()
|
||||
: std::vector<Data::UrlArea>();
|
||||
const auto &weatherAreas = story
|
||||
? story->weatherAreas()
|
||||
: std::vector<Data::WeatherArea>();
|
||||
if (_locations != locations) {
|
||||
_locations = locations;
|
||||
_areas.clear();
|
||||
|
@ -1062,13 +1066,18 @@ void Controller::updateAreas(Data::Story *story) {
|
|||
_urlAreas = urlAreas;
|
||||
_areas.clear();
|
||||
}
|
||||
if (_weatherAreas != weatherAreas) {
|
||||
_weatherAreas = weatherAreas;
|
||||
_areas.clear();
|
||||
}
|
||||
const auto reactionsCount = int(suggestedReactions.size());
|
||||
if (_suggestedReactions.size() == reactionsCount && !_areas.empty()) {
|
||||
for (auto i = 0; i != reactionsCount; ++i) {
|
||||
const auto count = suggestedReactions[i].count;
|
||||
if (_suggestedReactions[i].count != count) {
|
||||
_suggestedReactions[i].count = count;
|
||||
_areas[i + _locations.size()].reaction->updateCount(count);
|
||||
const auto view = _areas[i + _locations.size()].view.get();
|
||||
view->updateReactionsCount(count);
|
||||
}
|
||||
if (_suggestedReactions[i] != suggestedReactions[i]) {
|
||||
_suggestedReactions = suggestedReactions;
|
||||
|
@ -1206,7 +1215,8 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const {
|
|||
|| (_locations.empty()
|
||||
&& _suggestedReactions.empty()
|
||||
&& _channelPosts.empty()
|
||||
&& _urlAreas.empty())) {
|
||||
&& _urlAreas.empty()
|
||||
&& _weatherAreas.empty())) {
|
||||
return nullptr;
|
||||
} else if (_areas.empty()) {
|
||||
const auto now = story();
|
||||
|
@ -1240,7 +1250,7 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const {
|
|||
}
|
||||
}
|
||||
}),
|
||||
.reaction = std::move(widget),
|
||||
.view = std::move(widget),
|
||||
});
|
||||
}
|
||||
if (const auto session = now ? &now->session() : nullptr) {
|
||||
|
@ -1261,19 +1271,27 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const {
|
|||
.handler = std::make_shared<HiddenUrlClickHandler>(url.url),
|
||||
});
|
||||
}
|
||||
for (const auto &weather : _weatherAreas) {
|
||||
auto widget = _reactions->makeWeatherAreaWidget(weather);
|
||||
const auto raw = widget.get();
|
||||
_areas.push_back({
|
||||
.original = weather.area.geometry,
|
||||
.radiusOriginal = weather.area.radius,
|
||||
.rotation = weather.area.rotation,
|
||||
.handler = std::make_shared<LambdaClickHandler>([=] {
|
||||
raw->toggleMode();
|
||||
}),
|
||||
.view = std::move(widget),
|
||||
});
|
||||
}
|
||||
rebuildActiveAreas(*layout);
|
||||
}
|
||||
|
||||
const auto circleContains = [&](QRect circle) {
|
||||
const auto radius = std::min(circle.width(), circle.height()) / 2;
|
||||
const auto delta = circle.center() - point;
|
||||
return QPoint::dotProduct(delta, delta) < (radius * radius);
|
||||
};
|
||||
for (const auto &area : _areas) {
|
||||
const auto center = area.geometry.center();
|
||||
const auto angle = -area.rotation;
|
||||
const auto contains = area.reaction
|
||||
? circleContains(area.geometry)
|
||||
const auto contains = area.view
|
||||
? area.view->contains(point)
|
||||
: area.geometry.contains(Rotated(point, center, angle));
|
||||
if (contains) {
|
||||
return area.handler;
|
||||
|
|
|
@ -68,7 +68,7 @@ struct ContentLayout;
|
|||
class CaptionFullView;
|
||||
class RepostView;
|
||||
enum class ReactionsMode;
|
||||
class SuggestedReactionView;
|
||||
class StoryAreaView;
|
||||
struct RepostClickHandler;
|
||||
|
||||
enum class HeaderLayout {
|
||||
|
@ -208,10 +208,12 @@ private:
|
|||
};
|
||||
struct ActiveArea {
|
||||
QRectF original;
|
||||
float64 radiusOriginal = 0.;
|
||||
QRect geometry;
|
||||
float64 rotation = 0.;
|
||||
float64 radius = 0.;
|
||||
ClickHandlerPtr handler;
|
||||
std::unique_ptr<SuggestedReactionView> reaction;
|
||||
std::unique_ptr<StoryAreaView> view;
|
||||
};
|
||||
|
||||
void initLayout();
|
||||
|
@ -303,6 +305,7 @@ private:
|
|||
std::vector<Data::SuggestedReaction> _suggestedReactions;
|
||||
std::vector<Data::ChannelPost> _channelPosts;
|
||||
std::vector<Data::UrlArea> _urlAreas;
|
||||
std::vector<Data::WeatherArea> _weatherAreas;
|
||||
mutable std::vector<ActiveArea> _areas;
|
||||
|
||||
std::vector<CachedSource> _cachedSourcesList;
|
||||
|
|
|
@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "base/unixtime.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "chat_helpers/stickers_emoji_pack.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
|
@ -20,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/admin_log/history_admin_log_item.h"
|
||||
#include "history/view/media/history_view_custom_emoji.h"
|
||||
#include "history/view/media/history_view_media_unwrapped.h"
|
||||
#include "history/view/media/history_view_sticker_player.h"
|
||||
#include "history/view/reactions/history_view_reactions_selector.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/history_item_reply_markup.h"
|
||||
|
@ -61,7 +64,7 @@ constexpr auto kStoppingFadeDuration = crl::time(150);
|
|||
|
||||
class ReactionView final
|
||||
: public Ui::RpWidget
|
||||
, public SuggestedReactionView
|
||||
, public StoryAreaView
|
||||
, public HistoryView::DefaultElementDelegate {
|
||||
public:
|
||||
ReactionView(
|
||||
|
@ -69,9 +72,11 @@ public:
|
|||
not_null<Main::Session*> session,
|
||||
const Data::SuggestedReaction &reaction);
|
||||
|
||||
void setAreaGeometry(QRect geometry) override;
|
||||
void updateCount(int count) override;
|
||||
void setAreaGeometry(QRect geometry, float64 radius) override;
|
||||
void updateReactionsCount(int count) override;
|
||||
void playEffect() override;
|
||||
void toggleMode() override;
|
||||
bool contains(QPoint point) override;
|
||||
|
||||
private:
|
||||
using Element = HistoryView::Element;
|
||||
|
@ -108,6 +113,7 @@ private:
|
|||
Ui::Text::String _counter;
|
||||
Ui::Animations::Simple _counterAnimation;
|
||||
QRectF _bubbleGeometry;
|
||||
QRect _apiGeometry;
|
||||
int _size = 0;
|
||||
int _mediaLeft = 0;
|
||||
int _mediaTop = 0;
|
||||
|
@ -126,6 +132,58 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class WeatherView final : public Ui::RpWidget, public StoryAreaView {
|
||||
public:
|
||||
WeatherView(
|
||||
QWidget *parent,
|
||||
not_null<Main::Session*> session,
|
||||
const Data::WeatherArea &data);
|
||||
|
||||
void setAreaGeometry(QRect geometry, float64 radius) override;
|
||||
void updateReactionsCount(int count) override;
|
||||
void playEffect() override;
|
||||
void toggleMode() override;
|
||||
bool contains(QPoint point) override;
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
void cacheBackground();
|
||||
void watchForSticker();
|
||||
void setStickerFrom(not_null<DocumentData*> document);
|
||||
[[nodiscard]] QSize stickerSize() const;
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
Data::WeatherArea _data;
|
||||
EmojiPtr _emoji;
|
||||
QColor _fg;
|
||||
QImage _background;
|
||||
QFont _font;
|
||||
QRectF _rect;
|
||||
QRect _wrapped;
|
||||
float64 _radius = 0.;
|
||||
int _emojiSize = 0;
|
||||
int _padding = 0;
|
||||
bool _celsius = true;
|
||||
|
||||
std::shared_ptr<HistoryView::StickerPlayer> _sticker;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] QPoint Rotated(QPoint point, QPoint origin, float64 angle) {
|
||||
if (std::abs(angle) < 1.) {
|
||||
return point;
|
||||
}
|
||||
const auto alpha = angle / 180. * M_PI;
|
||||
const auto acos = cos(alpha);
|
||||
const auto asin = sin(alpha);
|
||||
point -= origin;
|
||||
return origin + QPoint(
|
||||
int(base::SafeRound(acos * point.x() - asin * point.y())),
|
||||
int(base::SafeRound(asin * point.x() + acos * point.y())));
|
||||
}
|
||||
|
||||
[[nodiscard]] AdminLog::OwnedItem GenerateFakeItem(
|
||||
not_null<HistoryView::ElementDelegate*> delegate,
|
||||
not_null<History*> history) {
|
||||
|
@ -140,6 +198,13 @@ private:
|
|||
return AdminLog::OwnedItem(delegate, item);
|
||||
}
|
||||
|
||||
[[nodiscard]] QColor ChooseWeatherFg(const QColor &bg) {
|
||||
const auto luminance = (0.2126 * bg.redF())
|
||||
+ (0.7152 * bg.greenF())
|
||||
+ (0.0722 * bg.blueF());
|
||||
return (luminance > 0.705) ? QColor(0, 0, 0) : QColor(255, 255, 255);
|
||||
}
|
||||
|
||||
ReactionView::ReactionView(
|
||||
QWidget *parent,
|
||||
not_null<Main::Session*> session,
|
||||
|
@ -198,7 +263,7 @@ ReactionView::ReactionView(
|
|||
}, lifetime());
|
||||
|
||||
_data.count = 0;
|
||||
updateCount(reaction.count);
|
||||
updateReactionsCount(reaction.count);
|
||||
_counterAnimation.stop();
|
||||
|
||||
setupCustomChatStylePalette();
|
||||
|
@ -212,7 +277,8 @@ void ReactionView::setupCustomChatStylePalette() {
|
|||
_chatStyle->applyCustomPalette(_chatStyle.get());
|
||||
}
|
||||
|
||||
void ReactionView::setAreaGeometry(QRect geometry) {
|
||||
void ReactionView::setAreaGeometry(QRect geometry, float64 radius) {
|
||||
_apiGeometry = geometry;
|
||||
_size = std::min(geometry.width(), geometry.height());
|
||||
_bubble = _size * kSuggestedBubbleSize;
|
||||
_bigOffset = _bubble * kSuggestedTailBigOffset;
|
||||
|
@ -228,7 +294,7 @@ void ReactionView::setAreaGeometry(QRect geometry) {
|
|||
updateEffectGeometry();
|
||||
}
|
||||
|
||||
void ReactionView::updateCount(int count) {
|
||||
void ReactionView::updateReactionsCount(int count) {
|
||||
if (_data.count == count) {
|
||||
return;
|
||||
}
|
||||
|
@ -283,6 +349,17 @@ void ReactionView::playEffect() {
|
|||
}
|
||||
}
|
||||
|
||||
void ReactionView::toggleMode() {
|
||||
Unexpected("ReactionView::toggleMode.");
|
||||
}
|
||||
|
||||
bool ReactionView::contains(QPoint point) {
|
||||
const auto circle = _apiGeometry;
|
||||
const auto radius = std::min(circle.width(), circle.height()) / 2;
|
||||
const auto delta = circle.center() - point;
|
||||
return QPoint::dotProduct(delta, delta) < (radius * radius);
|
||||
}
|
||||
|
||||
void ReactionView::paintEffectFrame(
|
||||
QPainter &p,
|
||||
not_null<Ui::ReactionFlyAnimation*> effect,
|
||||
|
@ -457,6 +534,205 @@ void ReactionView::cacheBackground() {
|
|||
paintShape(_data.dark ? dark : QColor(255, 255, 255));
|
||||
}
|
||||
|
||||
WeatherView::WeatherView(
|
||||
QWidget *parent,
|
||||
not_null<Main::Session*> session,
|
||||
const Data::WeatherArea &data)
|
||||
: RpWidget(parent)
|
||||
, _session(session)
|
||||
, _data(data)
|
||||
, _emoji(Ui::Emoji::Find(_data.emoji))
|
||||
, _fg(ChooseWeatherFg(_data.color)) {
|
||||
watchForSticker();
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
show();
|
||||
}
|
||||
|
||||
void WeatherView::watchForSticker() {
|
||||
if (!_emoji) {
|
||||
return;
|
||||
}
|
||||
const auto emojiStickers = &_session->emojiStickersPack();
|
||||
if (const auto sticker = emojiStickers->stickerForEmoji(_emoji)) {
|
||||
setStickerFrom(sticker.document);
|
||||
} else {
|
||||
emojiStickers->refreshed() | rpl::map([=] {
|
||||
return emojiStickers->stickerForEmoji(_emoji).document;
|
||||
}) | rpl::filter([=](DocumentData *document) {
|
||||
return document != nullptr;
|
||||
}) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](not_null<DocumentData*> document) {
|
||||
setStickerFrom(document);
|
||||
update();
|
||||
}, _lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
void WeatherView::setAreaGeometry(QRect geometry, float64 radius) {
|
||||
const auto diagxdiag = (geometry.width() * geometry.width())
|
||||
+ (geometry.height() * geometry.height());
|
||||
const auto diag = std::sqrt(diagxdiag);
|
||||
const auto topleft = QRectF(geometry).center()
|
||||
- QPointF(diag / 2., diag / 2.);
|
||||
const auto bottomright = topleft + QPointF(diag, diag);
|
||||
const auto left = int(std::floor(topleft.x()));
|
||||
const auto top = int(std::floor(topleft.y()));
|
||||
const auto right = int(std::ceil(bottomright.x()));
|
||||
const auto bottom = int(std::ceil(bottomright.y()));
|
||||
setGeometry(left, top, right - left, bottom - top);
|
||||
_rect = QRectF(geometry).translated(-left, -top);
|
||||
_radius = radius;
|
||||
|
||||
_emojiSize = int(base::SafeRound(_rect.height() * 2 / 3.));
|
||||
_font = st::semiboldFont->f;
|
||||
_font.setPixelSize(_emojiSize);
|
||||
_background = {};
|
||||
}
|
||||
|
||||
void WeatherView::updateReactionsCount(int count) {
|
||||
Unexpected("WeatherView::updateRactionsCount.");
|
||||
}
|
||||
|
||||
void WeatherView::playEffect() {
|
||||
Unexpected("WeatherView::playEffect.");
|
||||
}
|
||||
|
||||
void WeatherView::toggleMode() {
|
||||
_celsius = !_celsius;
|
||||
_background = {};
|
||||
update();
|
||||
}
|
||||
|
||||
bool WeatherView::contains(QPoint point) {
|
||||
const auto geometry = _rect.translated(pos()).toRect();
|
||||
const auto angle = -_data.area.rotation;
|
||||
return geometry.contains(Rotated(point, geometry.center(), angle));
|
||||
}
|
||||
|
||||
void WeatherView::paintEvent(QPaintEvent *e) {
|
||||
auto p = Painter(this);
|
||||
if (_background.size() != size() * style::DevicePixelRatio()) {
|
||||
cacheBackground();
|
||||
}
|
||||
p.drawImage(0, 0, _background);
|
||||
if (_sticker && _sticker->ready()) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto rcenter = _wrapped.center();
|
||||
p.translate(rcenter);
|
||||
p.rotate(_data.area.rotation);
|
||||
p.translate(-rcenter);
|
||||
|
||||
const auto image = _sticker->frame(
|
||||
stickerSize(),
|
||||
QColor(0, 0, 0, 0),
|
||||
false,
|
||||
crl::now(),
|
||||
false).image;
|
||||
const auto size = image.size() / style::DevicePixelRatio();
|
||||
const auto rect = QRectF(
|
||||
_wrapped.x() + _padding + (_emojiSize - size.width()) / 2.,
|
||||
_wrapped.y() + (_wrapped.height() - size.height()) / 2.,
|
||||
size.width(),
|
||||
size.height());
|
||||
const auto scenter = rect.center();
|
||||
const auto scale = (_emojiSize * 1.) / stickerSize().width();
|
||||
p.translate(scenter);
|
||||
p.scale(scale, scale);
|
||||
p.translate(-scenter);
|
||||
p.drawImage(rect, image);
|
||||
_sticker->markFrameShown();
|
||||
}
|
||||
}
|
||||
|
||||
QSize WeatherView::stickerSize() const {
|
||||
return QSize(st::chatIntroStickerSize, st::chatIntroStickerSize);
|
||||
}
|
||||
|
||||
void WeatherView::setStickerFrom(not_null<DocumentData*> document) {
|
||||
if (_sticker || !_emoji) {
|
||||
return;
|
||||
}
|
||||
const auto media = document->createMediaView();
|
||||
media->checkStickerLarge();
|
||||
media->goodThumbnailWanted();
|
||||
|
||||
rpl::single() | rpl::then(
|
||||
document->owner().session().downloaderTaskFinished()
|
||||
) | rpl::filter([=] {
|
||||
return media->loaded();
|
||||
}) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
const auto sticker = document->sticker();
|
||||
if (sticker->isLottie()) {
|
||||
_sticker = std::make_shared<HistoryView::LottiePlayer>(
|
||||
ChatHelpers::LottiePlayerFromDocument(
|
||||
media.get(),
|
||||
ChatHelpers::StickerLottieSize::StickerSet,
|
||||
stickerSize(),
|
||||
Lottie::Quality::High));
|
||||
} else if (sticker->isWebm()) {
|
||||
_sticker = std::make_shared<HistoryView::WebmPlayer>(
|
||||
media->owner()->location(),
|
||||
media->bytes(),
|
||||
stickerSize());
|
||||
} else {
|
||||
_sticker = std::make_shared<HistoryView::StaticStickerPlayer>(
|
||||
media->owner()->location(),
|
||||
media->bytes(),
|
||||
stickerSize());
|
||||
}
|
||||
_sticker->setRepaintCallback([=] { update(); });
|
||||
update();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void WeatherView::cacheBackground() {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
_background = QImage(
|
||||
size() * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_background.setDevicePixelRatio(ratio);
|
||||
_background.fill(Qt::transparent);
|
||||
|
||||
auto p = QPainter(&_background);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setBrush(_data.color);
|
||||
p.setPen(Qt::NoPen);
|
||||
const auto center = _rect.center();
|
||||
p.translate(center);
|
||||
p.rotate(_data.area.rotation);
|
||||
p.translate(-center);
|
||||
|
||||
const auto format = [](float64 value) {
|
||||
return QString::number(int(base::SafeRound(value * 10)) / 10.);
|
||||
};
|
||||
const auto text = [&] {
|
||||
const auto celsius = _data.millicelsius / 1000.;
|
||||
if (_celsius) {
|
||||
return format(celsius);
|
||||
}
|
||||
const auto fahrenheit = (celsius * 9.0 / 5.0) + 32;
|
||||
return format(fahrenheit);
|
||||
}().append(QChar(0xb0)).append(_celsius ? "C" : "F");
|
||||
const auto metrics = QFontMetrics(_font);
|
||||
const auto textWidth = metrics.horizontalAdvance(text);
|
||||
_padding = int(_rect.height() / 6);
|
||||
const auto fullWidth = (_emoji ? _emojiSize : 0)
|
||||
+ textWidth
|
||||
+ (2 * _padding);
|
||||
const auto left = _rect.x() + (_rect.width() - fullWidth) / 2;
|
||||
_wrapped = QRect(left, _rect.y(), fullWidth, _rect.height());
|
||||
|
||||
p.drawRoundedRect(_wrapped, _radius, _radius);
|
||||
|
||||
p.setPen(_fg);
|
||||
p.setFont(_font);
|
||||
p.drawText(_wrapped.marginsRemoved(
|
||||
{ _padding + (_emoji ? _emojiSize : 0), 0, _padding, 0 }),
|
||||
text,
|
||||
style::al_center);
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::ReactionId HeartReactionId() {
|
||||
return { QString() + QChar(10084) };
|
||||
}
|
||||
|
@ -804,13 +1080,21 @@ auto Reactions::chosen() const -> rpl::producer<Chosen> {
|
|||
|
||||
auto Reactions::makeSuggestedReactionWidget(
|
||||
const Data::SuggestedReaction &reaction)
|
||||
-> std::unique_ptr<SuggestedReactionView> {
|
||||
-> std::unique_ptr<StoryAreaView> {
|
||||
return std::make_unique<ReactionView>(
|
||||
_controller->wrap(),
|
||||
&_controller->uiShow()->session(),
|
||||
reaction);
|
||||
}
|
||||
|
||||
auto Reactions::makeWeatherAreaWidget(const Data::WeatherArea &data)
|
||||
-> std::unique_ptr<StoryAreaView> {
|
||||
return std::make_unique<WeatherView>(
|
||||
_controller->wrap(),
|
||||
&_controller->uiShow()->session(),
|
||||
data);
|
||||
}
|
||||
|
||||
void Reactions::setReplyFieldState(
|
||||
rpl::producer<bool> focused,
|
||||
rpl::producer<bool> hasSendText) {
|
||||
|
|
|
@ -16,6 +16,7 @@ struct ReactionId;
|
|||
class Session;
|
||||
class Story;
|
||||
struct SuggestedReaction;
|
||||
struct WeatherArea;
|
||||
} // namespace Data
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
|
@ -41,13 +42,15 @@ enum class ReactionsMode {
|
|||
Reaction,
|
||||
};
|
||||
|
||||
class SuggestedReactionView {
|
||||
class StoryAreaView {
|
||||
public:
|
||||
virtual ~SuggestedReactionView() = default;
|
||||
virtual ~StoryAreaView() = default;
|
||||
|
||||
virtual void setAreaGeometry(QRect geometry) = 0;
|
||||
virtual void updateCount(int count) = 0;
|
||||
virtual void setAreaGeometry(QRect geometry, float64 radius) = 0;
|
||||
virtual void updateReactionsCount(int count) = 0;
|
||||
virtual void playEffect() = 0;
|
||||
virtual void toggleMode() = 0;
|
||||
virtual bool contains(QPoint point) = 0;
|
||||
};
|
||||
|
||||
class Reactions final {
|
||||
|
@ -79,7 +82,9 @@ public:
|
|||
|
||||
[[nodiscard]] auto makeSuggestedReactionWidget(
|
||||
const Data::SuggestedReaction &reaction)
|
||||
-> std::unique_ptr<SuggestedReactionView>;
|
||||
-> std::unique_ptr<StoryAreaView>;
|
||||
[[nodiscard]] auto makeWeatherAreaWidget(const Data::WeatherArea &data)
|
||||
-> std::unique_ptr<StoryAreaView>;
|
||||
|
||||
void setReplyFieldState(
|
||||
rpl::producer<bool> focused,
|
||||
|
|
|
@ -22,4 +22,12 @@ std::optional<QColor> MaybeColorFromSerialized(quint32 serialized) {
|
|||
: std::make_optional(ColorFromSerialized(serialized));
|
||||
}
|
||||
|
||||
QColor Color32FromSerialized(quint32 serialized) {
|
||||
return QColor(
|
||||
int((serialized >> 24) & 0xFFU),
|
||||
int((serialized >> 16) & 0xFFU),
|
||||
int((serialized >> 8) & 0xFFU),
|
||||
int(serialized & 0xFFU));
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -12,5 +12,6 @@ namespace Ui {
|
|||
[[nodiscard]] QColor ColorFromSerialized(quint32 serialized);
|
||||
[[nodiscard]] std::optional<QColor> MaybeColorFromSerialized(
|
||||
quint32 serialized);
|
||||
[[nodiscard]] QColor Color32FromSerialized(quint32 serialized);
|
||||
|
||||
} // namespace Ui
|
||||
|
|
Loading…
Add table
Reference in a new issue