mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +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 {
|
return {
|
||||||
.geometry = { corner / 100., size / 100. },
|
.geometry = { corner / 100., size / 100. },
|
||||||
.rotation = data.vrotation().v,
|
.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) {
|
[[nodiscard]] TextWithEntities StripLinks(TextWithEntities text) {
|
||||||
const auto link = [&](const EntityInText &entity) {
|
const auto link = [&](const EntityInText &entity) {
|
||||||
return (entity.type() == EntityType::CustomUrl)
|
return (entity.type() == EntityType::CustomUrl)
|
||||||
|
@ -176,8 +173,11 @@ using UpdateFlag = StoryUpdate::Flag;
|
||||||
result.emplace(WeatherArea{
|
result.emplace(WeatherArea{
|
||||||
.area = ParseArea(data.vcoordinates()),
|
.area = ParseArea(data.vcoordinates()),
|
||||||
.emoji = qs(data.vemoji()),
|
.emoji = qs(data.vemoji()),
|
||||||
.color = Ui::ColorFromSerialized(data.vcolor().v),
|
.color = Ui::Color32FromSerialized(data.vcolor().v),
|
||||||
.millicelcius = int(data.vtemperature_c().v * 1000.),
|
.millicelsius = int(1000. * std::clamp(
|
||||||
|
data.vtemperature_c().v,
|
||||||
|
-274.,
|
||||||
|
1'000'000.)),
|
||||||
});
|
});
|
||||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||||
|
@ -721,6 +721,10 @@ const std::vector<UrlArea> &Story::urlAreas() const {
|
||||||
return _urlAreas;
|
return _urlAreas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<WeatherArea> &Story::weatherAreas() const {
|
||||||
|
return _weatherAreas;
|
||||||
|
}
|
||||||
|
|
||||||
void Story::applyChanges(
|
void Story::applyChanges(
|
||||||
StoryMedia media,
|
StoryMedia media,
|
||||||
const MTPDstoryItem &data,
|
const MTPDstoryItem &data,
|
||||||
|
@ -825,6 +829,7 @@ void Story::applyFields(
|
||||||
auto suggestedReactions = std::vector<SuggestedReaction>();
|
auto suggestedReactions = std::vector<SuggestedReaction>();
|
||||||
auto channelPosts = std::vector<ChannelPost>();
|
auto channelPosts = std::vector<ChannelPost>();
|
||||||
auto urlAreas = std::vector<UrlArea>();
|
auto urlAreas = std::vector<UrlArea>();
|
||||||
|
auto weatherAreas = std::vector<WeatherArea>();
|
||||||
if (const auto areas = data.vmedia_areas()) {
|
if (const auto areas = data.vmedia_areas()) {
|
||||||
for (const auto &area : areas->v) {
|
for (const auto &area : areas->v) {
|
||||||
if (const auto location = ParseLocation(area)) {
|
if (const auto location = ParseLocation(area)) {
|
||||||
|
@ -840,6 +845,8 @@ void Story::applyFields(
|
||||||
channelPosts.push_back(*post);
|
channelPosts.push_back(*post);
|
||||||
} else if (auto url = ParseUrlArea(area)) {
|
} else if (auto url = ParseUrlArea(area)) {
|
||||||
urlAreas.push_back(*url);
|
urlAreas.push_back(*url);
|
||||||
|
} else if (auto weather = ParseWeatherArea(area)) {
|
||||||
|
weatherAreas.push_back(*weather);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -853,6 +860,7 @@ void Story::applyFields(
|
||||||
= (_suggestedReactions != suggestedReactions);
|
= (_suggestedReactions != suggestedReactions);
|
||||||
const auto channelPostsChanged = (_channelPosts != channelPosts);
|
const auto channelPostsChanged = (_channelPosts != channelPosts);
|
||||||
const auto urlAreasChanged = (_urlAreas != urlAreas);
|
const auto urlAreasChanged = (_urlAreas != urlAreas);
|
||||||
|
const auto weatherAreasChanged = (_weatherAreas != weatherAreas);
|
||||||
const auto reactionChanged = (_sentReactionId != reaction);
|
const auto reactionChanged = (_sentReactionId != reaction);
|
||||||
|
|
||||||
_out = out;
|
_out = out;
|
||||||
|
@ -881,6 +889,9 @@ void Story::applyFields(
|
||||||
if (urlAreasChanged) {
|
if (urlAreasChanged) {
|
||||||
_urlAreas = std::move(urlAreas);
|
_urlAreas = std::move(urlAreas);
|
||||||
}
|
}
|
||||||
|
if (weatherAreasChanged) {
|
||||||
|
_weatherAreas = std::move(weatherAreas);
|
||||||
|
}
|
||||||
if (reactionChanged) {
|
if (reactionChanged) {
|
||||||
_sentReactionId = reaction;
|
_sentReactionId = reaction;
|
||||||
}
|
}
|
||||||
|
@ -891,7 +902,8 @@ void Story::applyFields(
|
||||||
|| mediaChanged
|
|| mediaChanged
|
||||||
|| locationsChanged
|
|| locationsChanged
|
||||||
|| channelPostsChanged
|
|| channelPostsChanged
|
||||||
|| urlAreasChanged;
|
|| urlAreasChanged
|
||||||
|
|| weatherAreasChanged;
|
||||||
const auto reactionsChanged = reactionChanged
|
const auto reactionsChanged = reactionChanged
|
||||||
|| suggestedReactionsChanged;
|
|| suggestedReactionsChanged;
|
||||||
if (!initial && (changed || reactionsChanged)) {
|
if (!initial && (changed || reactionsChanged)) {
|
||||||
|
|
|
@ -81,6 +81,7 @@ struct StoryViews {
|
||||||
struct StoryArea {
|
struct StoryArea {
|
||||||
QRectF geometry;
|
QRectF geometry;
|
||||||
float64 rotation = 0;
|
float64 rotation = 0;
|
||||||
|
float64 radius = 0;
|
||||||
|
|
||||||
friend inline bool operator==(
|
friend inline bool operator==(
|
||||||
const StoryArea &,
|
const StoryArea &,
|
||||||
|
@ -135,7 +136,7 @@ struct WeatherArea {
|
||||||
StoryArea area;
|
StoryArea area;
|
||||||
QString emoji;
|
QString emoji;
|
||||||
QColor color;
|
QColor color;
|
||||||
int millicelcius = 0;
|
int millicelsius = 0;
|
||||||
|
|
||||||
friend inline bool operator==(
|
friend inline bool operator==(
|
||||||
const WeatherArea &,
|
const WeatherArea &,
|
||||||
|
@ -219,6 +220,8 @@ public:
|
||||||
-> const std::vector<ChannelPost> &;
|
-> const std::vector<ChannelPost> &;
|
||||||
[[nodiscard]] auto urlAreas() const
|
[[nodiscard]] auto urlAreas() const
|
||||||
-> const std::vector<UrlArea> &;
|
-> const std::vector<UrlArea> &;
|
||||||
|
[[nodiscard]] auto weatherAreas() const
|
||||||
|
-> const std::vector<WeatherArea> &;
|
||||||
|
|
||||||
void applyChanges(
|
void applyChanges(
|
||||||
StoryMedia media,
|
StoryMedia media,
|
||||||
|
@ -270,6 +273,7 @@ private:
|
||||||
std::vector<SuggestedReaction> _suggestedReactions;
|
std::vector<SuggestedReaction> _suggestedReactions;
|
||||||
std::vector<ChannelPost> _channelPosts;
|
std::vector<ChannelPost> _channelPosts;
|
||||||
std::vector<UrlArea> _urlAreas;
|
std::vector<UrlArea> _urlAreas;
|
||||||
|
std::vector<WeatherArea> _weatherAreas;
|
||||||
StoryViews _views;
|
StoryViews _views;
|
||||||
StoryViews _channelReactions;
|
StoryViews _channelReactions;
|
||||||
const TimeId _date = 0;
|
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.width() * scale.width())),
|
||||||
int(base::SafeRound(general.height() * scale.height()))
|
int(base::SafeRound(general.height() * scale.height()))
|
||||||
).translated(origin);
|
).translated(origin);
|
||||||
if (const auto reaction = area.reaction.get()) {
|
area.radius = scale.width() * area.radiusOriginal / 100.;
|
||||||
reaction->setAreaGeometry(area.geometry);
|
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
|
const auto &urlAreas = story
|
||||||
? story->urlAreas()
|
? story->urlAreas()
|
||||||
: std::vector<Data::UrlArea>();
|
: std::vector<Data::UrlArea>();
|
||||||
|
const auto &weatherAreas = story
|
||||||
|
? story->weatherAreas()
|
||||||
|
: std::vector<Data::WeatherArea>();
|
||||||
if (_locations != locations) {
|
if (_locations != locations) {
|
||||||
_locations = locations;
|
_locations = locations;
|
||||||
_areas.clear();
|
_areas.clear();
|
||||||
|
@ -1062,13 +1066,18 @@ void Controller::updateAreas(Data::Story *story) {
|
||||||
_urlAreas = urlAreas;
|
_urlAreas = urlAreas;
|
||||||
_areas.clear();
|
_areas.clear();
|
||||||
}
|
}
|
||||||
|
if (_weatherAreas != weatherAreas) {
|
||||||
|
_weatherAreas = weatherAreas;
|
||||||
|
_areas.clear();
|
||||||
|
}
|
||||||
const auto reactionsCount = int(suggestedReactions.size());
|
const auto reactionsCount = int(suggestedReactions.size());
|
||||||
if (_suggestedReactions.size() == reactionsCount && !_areas.empty()) {
|
if (_suggestedReactions.size() == reactionsCount && !_areas.empty()) {
|
||||||
for (auto i = 0; i != reactionsCount; ++i) {
|
for (auto i = 0; i != reactionsCount; ++i) {
|
||||||
const auto count = suggestedReactions[i].count;
|
const auto count = suggestedReactions[i].count;
|
||||||
if (_suggestedReactions[i].count != count) {
|
if (_suggestedReactions[i].count != count) {
|
||||||
_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]) {
|
if (_suggestedReactions[i] != suggestedReactions[i]) {
|
||||||
_suggestedReactions = suggestedReactions;
|
_suggestedReactions = suggestedReactions;
|
||||||
|
@ -1206,7 +1215,8 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const {
|
||||||
|| (_locations.empty()
|
|| (_locations.empty()
|
||||||
&& _suggestedReactions.empty()
|
&& _suggestedReactions.empty()
|
||||||
&& _channelPosts.empty()
|
&& _channelPosts.empty()
|
||||||
&& _urlAreas.empty())) {
|
&& _urlAreas.empty()
|
||||||
|
&& _weatherAreas.empty())) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
} else if (_areas.empty()) {
|
} else if (_areas.empty()) {
|
||||||
const auto now = story();
|
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) {
|
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),
|
.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);
|
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) {
|
for (const auto &area : _areas) {
|
||||||
const auto center = area.geometry.center();
|
const auto center = area.geometry.center();
|
||||||
const auto angle = -area.rotation;
|
const auto angle = -area.rotation;
|
||||||
const auto contains = area.reaction
|
const auto contains = area.view
|
||||||
? circleContains(area.geometry)
|
? area.view->contains(point)
|
||||||
: area.geometry.contains(Rotated(point, center, angle));
|
: area.geometry.contains(Rotated(point, center, angle));
|
||||||
if (contains) {
|
if (contains) {
|
||||||
return area.handler;
|
return area.handler;
|
||||||
|
|
|
@ -68,7 +68,7 @@ struct ContentLayout;
|
||||||
class CaptionFullView;
|
class CaptionFullView;
|
||||||
class RepostView;
|
class RepostView;
|
||||||
enum class ReactionsMode;
|
enum class ReactionsMode;
|
||||||
class SuggestedReactionView;
|
class StoryAreaView;
|
||||||
struct RepostClickHandler;
|
struct RepostClickHandler;
|
||||||
|
|
||||||
enum class HeaderLayout {
|
enum class HeaderLayout {
|
||||||
|
@ -208,10 +208,12 @@ private:
|
||||||
};
|
};
|
||||||
struct ActiveArea {
|
struct ActiveArea {
|
||||||
QRectF original;
|
QRectF original;
|
||||||
|
float64 radiusOriginal = 0.;
|
||||||
QRect geometry;
|
QRect geometry;
|
||||||
float64 rotation = 0.;
|
float64 rotation = 0.;
|
||||||
|
float64 radius = 0.;
|
||||||
ClickHandlerPtr handler;
|
ClickHandlerPtr handler;
|
||||||
std::unique_ptr<SuggestedReactionView> reaction;
|
std::unique_ptr<StoryAreaView> view;
|
||||||
};
|
};
|
||||||
|
|
||||||
void initLayout();
|
void initLayout();
|
||||||
|
@ -303,6 +305,7 @@ private:
|
||||||
std::vector<Data::SuggestedReaction> _suggestedReactions;
|
std::vector<Data::SuggestedReaction> _suggestedReactions;
|
||||||
std::vector<Data::ChannelPost> _channelPosts;
|
std::vector<Data::ChannelPost> _channelPosts;
|
||||||
std::vector<Data::UrlArea> _urlAreas;
|
std::vector<Data::UrlArea> _urlAreas;
|
||||||
|
std::vector<Data::WeatherArea> _weatherAreas;
|
||||||
mutable std::vector<ActiveArea> _areas;
|
mutable std::vector<ActiveArea> _areas;
|
||||||
|
|
||||||
std::vector<CachedSource> _cachedSourcesList;
|
std::vector<CachedSource> _cachedSourcesList;
|
||||||
|
|
|
@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "boxes/premium_preview_box.h"
|
#include "boxes/premium_preview_box.h"
|
||||||
#include "chat_helpers/compose/compose_show.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_changes.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_document_media.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/admin_log/history_admin_log_item.h"
|
||||||
#include "history/view/media/history_view_custom_emoji.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_media_unwrapped.h"
|
||||||
|
#include "history/view/media/history_view_sticker_player.h"
|
||||||
#include "history/view/reactions/history_view_reactions_selector.h"
|
#include "history/view/reactions/history_view_reactions_selector.h"
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
#include "history/history_item_reply_markup.h"
|
#include "history/history_item_reply_markup.h"
|
||||||
|
@ -61,7 +64,7 @@ constexpr auto kStoppingFadeDuration = crl::time(150);
|
||||||
|
|
||||||
class ReactionView final
|
class ReactionView final
|
||||||
: public Ui::RpWidget
|
: public Ui::RpWidget
|
||||||
, public SuggestedReactionView
|
, public StoryAreaView
|
||||||
, public HistoryView::DefaultElementDelegate {
|
, public HistoryView::DefaultElementDelegate {
|
||||||
public:
|
public:
|
||||||
ReactionView(
|
ReactionView(
|
||||||
|
@ -69,9 +72,11 @@ public:
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
const Data::SuggestedReaction &reaction);
|
const Data::SuggestedReaction &reaction);
|
||||||
|
|
||||||
void setAreaGeometry(QRect geometry) override;
|
void setAreaGeometry(QRect geometry, float64 radius) override;
|
||||||
void updateCount(int count) override;
|
void updateReactionsCount(int count) override;
|
||||||
void playEffect() override;
|
void playEffect() override;
|
||||||
|
void toggleMode() override;
|
||||||
|
bool contains(QPoint point) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using Element = HistoryView::Element;
|
using Element = HistoryView::Element;
|
||||||
|
@ -108,6 +113,7 @@ private:
|
||||||
Ui::Text::String _counter;
|
Ui::Text::String _counter;
|
||||||
Ui::Animations::Simple _counterAnimation;
|
Ui::Animations::Simple _counterAnimation;
|
||||||
QRectF _bubbleGeometry;
|
QRectF _bubbleGeometry;
|
||||||
|
QRect _apiGeometry;
|
||||||
int _size = 0;
|
int _size = 0;
|
||||||
int _mediaLeft = 0;
|
int _mediaLeft = 0;
|
||||||
int _mediaTop = 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(
|
[[nodiscard]] AdminLog::OwnedItem GenerateFakeItem(
|
||||||
not_null<HistoryView::ElementDelegate*> delegate,
|
not_null<HistoryView::ElementDelegate*> delegate,
|
||||||
not_null<History*> history) {
|
not_null<History*> history) {
|
||||||
|
@ -140,6 +198,13 @@ private:
|
||||||
return AdminLog::OwnedItem(delegate, item);
|
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(
|
ReactionView::ReactionView(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
|
@ -198,7 +263,7 @@ ReactionView::ReactionView(
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_data.count = 0;
|
_data.count = 0;
|
||||||
updateCount(reaction.count);
|
updateReactionsCount(reaction.count);
|
||||||
_counterAnimation.stop();
|
_counterAnimation.stop();
|
||||||
|
|
||||||
setupCustomChatStylePalette();
|
setupCustomChatStylePalette();
|
||||||
|
@ -212,7 +277,8 @@ void ReactionView::setupCustomChatStylePalette() {
|
||||||
_chatStyle->applyCustomPalette(_chatStyle.get());
|
_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());
|
_size = std::min(geometry.width(), geometry.height());
|
||||||
_bubble = _size * kSuggestedBubbleSize;
|
_bubble = _size * kSuggestedBubbleSize;
|
||||||
_bigOffset = _bubble * kSuggestedTailBigOffset;
|
_bigOffset = _bubble * kSuggestedTailBigOffset;
|
||||||
|
@ -228,7 +294,7 @@ void ReactionView::setAreaGeometry(QRect geometry) {
|
||||||
updateEffectGeometry();
|
updateEffectGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReactionView::updateCount(int count) {
|
void ReactionView::updateReactionsCount(int count) {
|
||||||
if (_data.count == count) {
|
if (_data.count == count) {
|
||||||
return;
|
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(
|
void ReactionView::paintEffectFrame(
|
||||||
QPainter &p,
|
QPainter &p,
|
||||||
not_null<Ui::ReactionFlyAnimation*> effect,
|
not_null<Ui::ReactionFlyAnimation*> effect,
|
||||||
|
@ -457,6 +534,205 @@ void ReactionView::cacheBackground() {
|
||||||
paintShape(_data.dark ? dark : QColor(255, 255, 255));
|
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() {
|
[[nodiscard]] Data::ReactionId HeartReactionId() {
|
||||||
return { QString() + QChar(10084) };
|
return { QString() + QChar(10084) };
|
||||||
}
|
}
|
||||||
|
@ -804,13 +1080,21 @@ auto Reactions::chosen() const -> rpl::producer<Chosen> {
|
||||||
|
|
||||||
auto Reactions::makeSuggestedReactionWidget(
|
auto Reactions::makeSuggestedReactionWidget(
|
||||||
const Data::SuggestedReaction &reaction)
|
const Data::SuggestedReaction &reaction)
|
||||||
-> std::unique_ptr<SuggestedReactionView> {
|
-> std::unique_ptr<StoryAreaView> {
|
||||||
return std::make_unique<ReactionView>(
|
return std::make_unique<ReactionView>(
|
||||||
_controller->wrap(),
|
_controller->wrap(),
|
||||||
&_controller->uiShow()->session(),
|
&_controller->uiShow()->session(),
|
||||||
reaction);
|
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(
|
void Reactions::setReplyFieldState(
|
||||||
rpl::producer<bool> focused,
|
rpl::producer<bool> focused,
|
||||||
rpl::producer<bool> hasSendText) {
|
rpl::producer<bool> hasSendText) {
|
||||||
|
|
|
@ -16,6 +16,7 @@ struct ReactionId;
|
||||||
class Session;
|
class Session;
|
||||||
class Story;
|
class Story;
|
||||||
struct SuggestedReaction;
|
struct SuggestedReaction;
|
||||||
|
struct WeatherArea;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace HistoryView::Reactions {
|
namespace HistoryView::Reactions {
|
||||||
|
@ -41,13 +42,15 @@ enum class ReactionsMode {
|
||||||
Reaction,
|
Reaction,
|
||||||
};
|
};
|
||||||
|
|
||||||
class SuggestedReactionView {
|
class StoryAreaView {
|
||||||
public:
|
public:
|
||||||
virtual ~SuggestedReactionView() = default;
|
virtual ~StoryAreaView() = default;
|
||||||
|
|
||||||
virtual void setAreaGeometry(QRect geometry) = 0;
|
virtual void setAreaGeometry(QRect geometry, float64 radius) = 0;
|
||||||
virtual void updateCount(int count) = 0;
|
virtual void updateReactionsCount(int count) = 0;
|
||||||
virtual void playEffect() = 0;
|
virtual void playEffect() = 0;
|
||||||
|
virtual void toggleMode() = 0;
|
||||||
|
virtual bool contains(QPoint point) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Reactions final {
|
class Reactions final {
|
||||||
|
@ -79,7 +82,9 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] auto makeSuggestedReactionWidget(
|
[[nodiscard]] auto makeSuggestedReactionWidget(
|
||||||
const Data::SuggestedReaction &reaction)
|
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(
|
void setReplyFieldState(
|
||||||
rpl::producer<bool> focused,
|
rpl::producer<bool> focused,
|
||||||
|
|
|
@ -22,4 +22,12 @@ std::optional<QColor> MaybeColorFromSerialized(quint32 serialized) {
|
||||||
: std::make_optional(ColorFromSerialized(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
|
} // namespace Ui
|
||||||
|
|
|
@ -12,5 +12,6 @@ namespace Ui {
|
||||||
[[nodiscard]] QColor ColorFromSerialized(quint32 serialized);
|
[[nodiscard]] QColor ColorFromSerialized(quint32 serialized);
|
||||||
[[nodiscard]] std::optional<QColor> MaybeColorFromSerialized(
|
[[nodiscard]] std::optional<QColor> MaybeColorFromSerialized(
|
||||||
quint32 serialized);
|
quint32 serialized);
|
||||||
|
[[nodiscard]] QColor Color32FromSerialized(quint32 serialized);
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
Loading…
Add table
Reference in a new issue