1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "window/section_widget.h"
9
10 #include "mainwidget.h"
11 #include "ui/ui_utility.h"
12 #include "ui/chat/chat_theme.h"
13 #include "data/data_peer.h"
14 #include "data/data_changes.h"
15 #include "data/data_session.h"
16 #include "data/data_cloud_themes.h"
17 #include "main/main_session.h"
18 #include "window/section_memento.h"
19 #include "window/window_slide_animation.h"
20 #include "window/window_session_controller.h"
21 #include "window/themes/window_theme.h"
22
23 #include <rpl/range.h>
24
25 namespace Window {
26 namespace {
27
PeerThemeEmojiValue(not_null<PeerData * > peer)28 [[nodiscard]] rpl::producer<QString> PeerThemeEmojiValue(
29 not_null<PeerData*> peer) {
30 return peer->session().changes().peerFlagsValue(
31 peer,
32 Data::PeerUpdate::Flag::ChatThemeEmoji
33 ) | rpl::map([=] {
34 return peer->themeEmoji();
35 });
36 }
37
MaybeChatThemeDataValueFromPeer(not_null<PeerData * > peer)38 [[nodiscard]] auto MaybeChatThemeDataValueFromPeer(
39 not_null<PeerData*> peer)
40 -> rpl::producer<std::optional<Data::CloudTheme>> {
41 return PeerThemeEmojiValue(
42 peer
43 ) | rpl::map([=](const QString &emoji)
44 -> rpl::producer<std::optional<Data::CloudTheme>> {
45 return peer->owner().cloudThemes().themeForEmojiValue(emoji);
46 }) | rpl::flatten_latest();
47 }
48
49 struct ResolvedTheme {
50 std::optional<Data::CloudTheme> theme;
51 bool dark = false;
52 };
53
MaybeCloudThemeValueFromPeer(not_null<PeerData * > peer)54 [[nodiscard]] auto MaybeCloudThemeValueFromPeer(
55 not_null<PeerData*> peer)
56 -> rpl::producer<ResolvedTheme> {
57 return rpl::combine(
58 MaybeChatThemeDataValueFromPeer(peer),
59 Theme::IsThemeDarkValue() | rpl::distinct_until_changed()
60 ) | rpl::map([](std::optional<Data::CloudTheme> theme, bool night) {
61 return ResolvedTheme{ std::move(theme), night };
62 });
63 }
64
65 } // namespace
66
AbstractSectionWidget(QWidget * parent,not_null<SessionController * > controller,rpl::producer<PeerData * > peerForBackground)67 AbstractSectionWidget::AbstractSectionWidget(
68 QWidget *parent,
69 not_null<SessionController*> controller,
70 rpl::producer<PeerData*> peerForBackground)
71 : RpWidget(parent)
72 , _controller(controller) {
73 std::move(
74 peerForBackground
75 ) | rpl::map([=](PeerData *peer) -> rpl::producer<> {
76 if (!peer) {
77 return rpl::single(
78 rpl::empty_value()
79 ) | rpl::then(
80 controller->defaultChatTheme()->repaintBackgroundRequests()
81 );
82 }
83 return ChatThemeValueFromPeer(
84 controller,
85 peer
86 ) | rpl::map([](const std::shared_ptr<Ui::ChatTheme> &theme) {
87 return rpl::single(
88 rpl::empty_value()
89 ) | rpl::then(
90 theme->repaintBackgroundRequests()
91 );
92 }) | rpl::flatten_latest();
93 }) | rpl::flatten_latest() | rpl::start_with_next([=] {
94 update();
95 }, lifetime());
96 }
97
session() const98 Main::Session &AbstractSectionWidget::session() const {
99 return _controller->session();
100 }
101
SectionWidget(QWidget * parent,not_null<Window::SessionController * > controller,rpl::producer<PeerData * > peerForBackground)102 SectionWidget::SectionWidget(
103 QWidget *parent,
104 not_null<Window::SessionController*> controller,
105 rpl::producer<PeerData*> peerForBackground)
106 : AbstractSectionWidget(parent, controller, std::move(peerForBackground)) {
107 }
108
SectionWidget(QWidget * parent,not_null<Window::SessionController * > controller,not_null<PeerData * > peerForBackground)109 SectionWidget::SectionWidget(
110 QWidget *parent,
111 not_null<Window::SessionController*> controller,
112 not_null<PeerData*> peerForBackground)
113 : AbstractSectionWidget(
114 parent,
115 controller,
116 rpl::single(peerForBackground.get())) {
117 }
118
setGeometryWithTopMoved(const QRect & newGeometry,int topDelta)119 void SectionWidget::setGeometryWithTopMoved(
120 const QRect &newGeometry,
121 int topDelta) {
122 _topDelta = topDelta;
123 bool willBeResized = (size() != newGeometry.size());
124 if (geometry() != newGeometry) {
125 auto weak = Ui::MakeWeak(this);
126 setGeometry(newGeometry);
127 if (!weak) {
128 return;
129 }
130 }
131 if (!willBeResized) {
132 resizeEvent(nullptr);
133 }
134 _topDelta = 0;
135 }
136
showAnimated(SlideDirection direction,const SectionSlideParams & params)137 void SectionWidget::showAnimated(
138 SlideDirection direction,
139 const SectionSlideParams ¶ms) {
140 if (_showAnimation) return;
141
142 showChildren();
143 auto myContentCache = grabForShowAnimation(params);
144 hideChildren();
145 showAnimatedHook(params);
146
147 _showAnimation = std::make_unique<SlideAnimation>();
148 _showAnimation->setDirection(direction);
149 _showAnimation->setRepaintCallback([this] { update(); });
150 _showAnimation->setFinishedCallback([this] { showFinished(); });
151 _showAnimation->setPixmaps(
152 params.oldContentCache,
153 myContentCache);
154 _showAnimation->setTopBarShadow(params.withTopBarShadow);
155 _showAnimation->setWithFade(params.withFade);
156 _showAnimation->start();
157
158 show();
159 }
160
createMemento()161 std::shared_ptr<SectionMemento> SectionWidget::createMemento() {
162 return nullptr;
163 }
164
showFast()165 void SectionWidget::showFast() {
166 show();
167 showFinished();
168 }
169
grabForShowAnimation(const SectionSlideParams & params)170 QPixmap SectionWidget::grabForShowAnimation(
171 const SectionSlideParams ¶ms) {
172 return Ui::GrabWidget(this);
173 }
174
PaintBackground(not_null<Window::SessionController * > controller,not_null<Ui::ChatTheme * > theme,not_null<QWidget * > widget,QRect clip)175 void SectionWidget::PaintBackground(
176 not_null<Window::SessionController*> controller,
177 not_null<Ui::ChatTheme*> theme,
178 not_null<QWidget*> widget,
179 QRect clip) {
180 Painter p(widget);
181
182 const auto &background = theme->background();
183 if (background.colorForFill) {
184 p.fillRect(clip, *background.colorForFill);
185 return;
186 }
187 const auto &gradient = background.gradientForFill;
188 const auto fill = QSize(widget->width(), controller->content()->height());
189 auto fromy = controller->content()->backgroundFromY();
190 auto state = theme->backgroundState(fill);
191 const auto paintCache = [&](const Ui::CachedBackground &cache) {
192 const auto to = QRect(
193 QPoint(cache.x, fromy + cache.y),
194 cache.pixmap.size() / cIntRetinaFactor());
195 if (cache.waitingForNegativePattern) {
196 // While we wait for pattern being loaded we paint just gradient.
197 // But in case of negative patter opacity we just fill-black.
198 p.fillRect(to, Qt::black);
199 } else if (cache.area == fill) {
200 p.drawPixmap(to, cache.pixmap);
201 } else {
202 const auto sx = fill.width() / float64(cache.area.width());
203 const auto sy = fill.height() / float64(cache.area.height());
204 const auto round = [](float64 value) -> int {
205 return (value >= 0.)
206 ? int(std::ceil(value))
207 : int(std::floor(value));
208 };
209 const auto sto = QPoint(round(to.x() * sx), round(to.y() * sy));
210 p.drawPixmap(
211 sto.x(),
212 sto.y(),
213 round((to.x() + to.width()) * sx) - sto.x(),
214 round((to.y() + to.height()) * sy) - sto.y(),
215 cache.pixmap);
216 }
217 };
218 const auto hasNow = !state.now.pixmap.isNull();
219 const auto goodNow = hasNow && (state.now.area == fill);
220 const auto useCache = goodNow || !gradient.isNull();
221 if (useCache) {
222 if (state.shown < 1. && !gradient.isNull()) {
223 paintCache(state.was);
224 p.setOpacity(state.shown);
225 }
226 paintCache(state.now);
227 return;
228 }
229 const auto &prepared = background.prepared;
230 if (prepared.isNull()) {
231 return;
232 } else if (background.isPattern) {
233 const auto w = prepared.width() * fill.height() / prepared.height();
234 const auto cx = qCeil(fill.width() / float64(w));
235 const auto cols = (cx / 2) * 2 + 1;
236 const auto xshift = (fill.width() - w * cols) / 2;
237 for (auto i = 0; i != cols; ++i) {
238 p.drawImage(
239 QRect(xshift + i * w, 0, w, fill.height()),
240 prepared,
241 QRect(QPoint(), prepared.size()));
242 }
243 } else if (background.tile) {
244 const auto &tiled = background.preparedForTiled;
245 const auto left = clip.left();
246 const auto top = clip.top();
247 const auto right = clip.left() + clip.width();
248 const auto bottom = clip.top() + clip.height();
249 const auto w = tiled.width() / cRetinaFactor();
250 const auto h = tiled.height() / cRetinaFactor();
251 const auto sx = qFloor(left / w);
252 const auto sy = qFloor((top - fromy) / h);
253 const auto cx = qCeil(right / w);
254 const auto cy = qCeil((bottom - fromy) / h);
255 for (auto i = sx; i < cx; ++i) {
256 for (auto j = sy; j < cy; ++j) {
257 p.drawImage(QPointF(i * w, fromy + j * h), tiled);
258 }
259 }
260 } else {
261 const auto hq = PainterHighQualityEnabler(p);
262 const auto rects = Ui::ComputeChatBackgroundRects(
263 fill,
264 prepared.size());
265 auto to = rects.to;
266 to.moveTop(to.top() + fromy);
267 p.drawImage(to, prepared, rects.from);
268 }
269 }
270
paintEvent(QPaintEvent * e)271 void SectionWidget::paintEvent(QPaintEvent *e) {
272 if (_showAnimation) {
273 Painter p(this);
274 _showAnimation->paintContents(p, e->rect());
275 }
276 }
277
showFinished()278 void SectionWidget::showFinished() {
279 _showAnimation.reset();
280 if (isHidden()) return;
281
282 showChildren();
283 showFinishedHook();
284
285 setInnerFocus();
286 }
287
desiredHeight() const288 rpl::producer<int> SectionWidget::desiredHeight() const {
289 return rpl::single(height());
290 }
291
292 SectionWidget::~SectionWidget() = default;
293
ChatThemeValueFromPeer(not_null<SessionController * > controller,not_null<PeerData * > peer)294 auto ChatThemeValueFromPeer(
295 not_null<SessionController*> controller,
296 not_null<PeerData*> peer)
297 -> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
298 auto cloud = MaybeCloudThemeValueFromPeer(
299 peer
300 ) | rpl::map([=](ResolvedTheme resolved)
301 -> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
302 return resolved.theme
303 ? controller->cachedChatThemeValue(
304 *resolved.theme,
305 (resolved.dark
306 ? Data::CloudThemeType::Dark
307 : Data::CloudThemeType::Light))
308 : rpl::single(controller->defaultChatTheme());
309 }) | rpl::flatten_latest(
310 ) | rpl::distinct_until_changed();
311
312 return rpl::combine(
313 std::move(cloud),
314 controller->peerThemeOverrideValue()
315 ) | rpl::map([=](
316 std::shared_ptr<Ui::ChatTheme> &&cloud,
317 PeerThemeOverride &&overriden) {
318 return (overriden.peer == peer.get())
319 ? std::move(overriden.theme)
320 : std::move(cloud);
321 });
322 }
323
324 } // namespace Window
325