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 "settings/settings_intro.h"
9 
10 #include "settings/settings_common.h"
11 #include "settings/settings_advanced.h"
12 #include "settings/settings_main.h"
13 #include "settings/settings_chat.h"
14 #include "settings/settings_codes.h"
15 #include "ui/wrap/fade_wrap.h"
16 #include "ui/wrap/vertical_layout.h"
17 #include "ui/widgets/shadow.h"
18 #include "ui/widgets/labels.h"
19 #include "ui/widgets/buttons.h"
20 #include "ui/widgets/scroll_area.h"
21 #include "ui/cached_round_corners.h"
22 #include "lang/lang_keys.h"
23 #include "boxes/abstract_box.h"
24 #include "window/window_controller.h"
25 #include "styles/style_settings.h"
26 #include "styles/style_layers.h"
27 #include "styles/style_info.h"
28 
29 namespace Settings {
30 namespace {
31 
32 class TopBar : public Ui::RpWidget {
33 public:
34 	TopBar(QWidget *parent, const style::InfoTopBar &st);
35 
36 	void setTitle(rpl::producer<QString> &&title);
37 
38 	template <typename ButtonWidget>
addButton(base::unique_qptr<ButtonWidget> button)39 	ButtonWidget *addButton(base::unique_qptr<ButtonWidget> button) {
40 		auto result = button.get();
41 		pushButton(std::move(button));
42 		return result;
43 	}
44 
45 protected:
46 	int resizeGetHeight(int newWidth) override;
47 	void paintEvent(QPaintEvent *e) override;
48 
49 private:
50 	void updateControlsGeometry(int newWidth);
51 	Ui::RpWidget *pushButton(base::unique_qptr<Ui::RpWidget> button);
52 
53 	const style::InfoTopBar &_st;
54 	std::vector<base::unique_qptr<Ui::RpWidget>> _buttons;
55 	QPointer<Ui::FlatLabel> _title;
56 
57 };
58 
CreateIntroSettings(QWidget * parent,not_null<Window::Controller * > window)59 object_ptr<Ui::RpWidget> CreateIntroSettings(
60 		QWidget *parent,
61 		not_null<Window::Controller*> window) {
62 	auto result = object_ptr<Ui::VerticalLayout>(parent);
63 
64 	AddDivider(result);
65 	AddSkip(result);
66 	SetupLanguageButton(result, false);
67 	SetupConnectionType(window, &window->account(), result);
68 	AddSkip(result);
69 	if (HasUpdate()) {
70 		AddDivider(result);
71 		AddSkip(result);
72 		SetupUpdate(result);
73 		AddSkip(result);
74 	}
75 	{
76 		auto wrap = object_ptr<Ui::VerticalLayout>(result);
77 		SetupSystemIntegrationContent(
78 			window->sessionController(),
79 			wrap.data());
80 		if (wrap->count() > 0) {
81 			AddDivider(result);
82 			AddSkip(result);
83 			result->add(object_ptr<Ui::OverrideMargins>(
84 				result,
85 				std::move(wrap)));
86 			AddSkip(result);
87 		}
88 	}
89 	AddDivider(result);
90 	AddSkip(result);
91 	SetupInterfaceScale(window, result, false);
92 	SetupDefaultThemes(window, result);
93 	AddSkip(result);
94 
95 	if (anim::Disabled()) {
96 		AddDivider(result);
97 		AddSkip(result);
98 		SetupAnimations(result);
99 		AddSkip(result);
100 	}
101 
102 	AddDivider(result);
103 	AddSkip(result);
104 	SetupFaq(result, false);
105 	AddSkip(result);
106 
107 	return result;
108 }
109 
TopBar(QWidget * parent,const style::InfoTopBar & st)110 TopBar::TopBar(QWidget *parent, const style::InfoTopBar &st)
111 : RpWidget(parent)
112 , _st(st) {
113 	setAttribute(Qt::WA_OpaquePaintEvent);
114 }
115 
setTitle(rpl::producer<QString> && title)116 void TopBar::setTitle(rpl::producer<QString> &&title) {
117 	if (_title) {
118 		delete _title;
119 	}
120 	_title = Ui::CreateChild<Ui::FlatLabel>(
121 		this,
122 		std::move(title),
123 		_st.title);
124 	updateControlsGeometry(width());
125 }
126 
pushButton(base::unique_qptr<Ui::RpWidget> button)127 Ui::RpWidget *TopBar::pushButton(base::unique_qptr<Ui::RpWidget> button) {
128 	auto wrapped = std::move(button);
129 	auto weak = wrapped.get();
130 	_buttons.push_back(std::move(wrapped));
131 	weak->widthValue(
132 	) | rpl::start_with_next([this] {
133 		updateControlsGeometry(width());
134 	}, lifetime());
135 	return weak;
136 }
137 
resizeGetHeight(int newWidth)138 int TopBar::resizeGetHeight(int newWidth) {
139 	updateControlsGeometry(newWidth);
140 	return _st.height;
141 }
142 
updateControlsGeometry(int newWidth)143 void TopBar::updateControlsGeometry(int newWidth) {
144 	auto right = 0;
145 	for (auto &button : _buttons) {
146 		if (!button) continue;
147 		button->moveToRight(right, 0, newWidth);
148 		right += button->width();
149 	}
150 	if (_title) {
151 		_title->moveToLeft(
152 			_st.titlePosition.x(),
153 			_st.titlePosition.y(),
154 			newWidth);
155 	}
156 }
157 
paintEvent(QPaintEvent * e)158 void TopBar::paintEvent(QPaintEvent *e) {
159 	Painter p(this);
160 	p.fillRect(e->rect(), _st.bg);
161 }
162 
163 } // namespace
164 
165 class IntroWidget : public Ui::RpWidget {
166 public:
167 	IntroWidget(
168 		QWidget *parent,
169 		not_null<Window::Controller*> window);
170 
171 	void forceContentRepaint();
172 
173 	rpl::producer<int> desiredHeightValue() const override;
174 
175 	void updateGeometry(QRect newGeometry, int additionalScroll);
176 	int scrollTillBottom(int forHeight) const;
177 	rpl::producer<int>  scrollTillBottomChanges() const;
178 
179 	void setInnerFocus();
180 
181 	~IntroWidget();
182 
183 protected:
184 	void resizeEvent(QResizeEvent *e) override;
185 	void keyPressEvent(QKeyEvent *e) override;
186 
187 private:
188 	void updateControlsGeometry();
189 	QRect contentGeometry() const;
190 	void setInnerWidget(object_ptr<Ui::RpWidget> content);
191 	void showContent(not_null<Window::Controller*> window);
192 	rpl::producer<bool> topShadowToggledValue() const;
193 	void createTopBar();
194 	void applyAdditionalScroll(int additionalScroll);
195 
196 	rpl::variable<int> _scrollTopSkip = -1;
197 	rpl::event_stream<int> _scrollTillBottomChanges;
198 	object_ptr<Ui::RpWidget> _wrap;
199 	not_null<Ui::ScrollArea*> _scroll;
200 	Ui::PaddingWrap<Ui::RpWidget> *_innerWrap = nullptr;
201 	int _innerDesiredHeight = 0;
202 
203 	int _additionalScroll = 0;
204 	object_ptr<TopBar> _topBar = { nullptr };
205 
206 	object_ptr<Ui::FadeShadow> _topShadow;
207 
208 };
209 
IntroWidget(QWidget * parent,not_null<Window::Controller * > window)210 IntroWidget::IntroWidget(
211 	QWidget *parent,
212 	not_null<Window::Controller*> window)
213 : RpWidget(parent)
214 , _wrap(this)
215 , _scroll(Ui::CreateChild<Ui::ScrollArea>(_wrap.data()))
216 , _topShadow(this) {
217 	_wrap->setAttribute(Qt::WA_OpaquePaintEvent);
218 	_wrap->paintRequest(
219 	) | rpl::start_with_next([=](QRect clip) {
220 		Painter p(_wrap.data());
221 		p.fillRect(clip, st::boxBg);
222 	}, _wrap->lifetime());
223 
224 	_scrollTopSkip.changes(
225 	) | rpl::start_with_next([this] {
226 		updateControlsGeometry();
227 	}, lifetime());
228 
229 	createTopBar();
230 	showContent(window);
231 	_topShadow->toggleOn(
232 		topShadowToggledValue(
233 		) | rpl::filter([](bool shown) {
234 			return true;
235 		}));
236 }
237 
updateControlsGeometry()238 void IntroWidget::updateControlsGeometry() {
239 	if (!_innerWrap) {
240 		return;
241 	}
242 
243 	_topBar->resizeToWidth(width());
244 	_topShadow->resizeToWidth(width());
245 	_topShadow->moveToLeft(0, _topBar->height());
246 	_wrap->setGeometry(contentGeometry());
247 
248 	auto scrollGeometry = _wrap->rect().marginsRemoved(
249 		QMargins(0, _scrollTopSkip.current(), 0, 0));
250 	if (_scroll->geometry() != scrollGeometry) {
251 		_scroll->setGeometry(scrollGeometry);
252 		_innerWrap->resizeToWidth(_scroll->width());
253 	}
254 
255 	if (!_scroll->isHidden()) {
256 		auto scrollTop = _scroll->scrollTop();
257 		_innerWrap->setVisibleTopBottom(
258 			scrollTop,
259 			scrollTop + _scroll->height());
260 	}
261 }
262 
forceContentRepaint()263 void IntroWidget::forceContentRepaint() {
264 	// WA_OpaquePaintEvent on TopBar creates render glitches when
265 	// animating the LayerWidget's height :( Fixing by repainting.
266 
267 	if (_topBar) {
268 		_topBar->update();
269 	}
270 	_scroll->update();
271 	if (_innerWrap) {
272 		_innerWrap->update();
273 	}
274 }
275 
createTopBar()276 void IntroWidget::createTopBar() {
277 	_topBar.create(this, st::infoLayerTopBar);
278 	_topBar->setTitle(tr::lng_menu_settings());
279 	auto close = _topBar->addButton(
280 		base::make_unique_q<Ui::IconButton>(
281 			_topBar,
282 			st::infoLayerTopBarClose));
283 	close->addClickHandler([] {
284 		Ui::hideSettingsAndLayer();
285 	});
286 
287 	_topBar->lower();
288 	_topBar->resizeToWidth(width());
289 	_topBar->show();
290 }
291 
setInnerWidget(object_ptr<Ui::RpWidget> content)292 void IntroWidget::setInnerWidget(object_ptr<Ui::RpWidget> content) {
293 	_innerWrap = _scroll->setOwnedWidget(
294 		object_ptr<Ui::PaddingWrap<Ui::RpWidget>>(
295 			this,
296 			std::move(content),
297 			_innerWrap ? _innerWrap->padding() : style::margins()));
298 	_innerWrap->move(0, 0);
299 
300 	// MSVC BUG + REGRESSION rpl::mappers::tuple :(
301 	rpl::combine(
302 		_scroll->scrollTopValue(),
303 		_scroll->heightValue(),
304 		_innerWrap->entity()->desiredHeightValue()
305 	) | rpl::start_with_next([this](
306 			int top,
307 			int height,
308 			int desired) {
309 		const auto bottom = top + height;
310 		_innerDesiredHeight = desired;
311 		_innerWrap->setVisibleTopBottom(top, bottom);
312 		_scrollTillBottomChanges.fire_copy(std::max(desired - bottom, 0));
313 	}, _innerWrap->lifetime());
314 }
315 
topShadowToggledValue() const316 rpl::producer<bool> IntroWidget::topShadowToggledValue() const {
317 	using namespace rpl::mappers;
318 	return rpl::combine(
319 		_scroll->scrollTopValue(),
320 		_scrollTopSkip.value()
321 	) | rpl::map((_1 > 0) || (_2 > 0));
322 }
323 
showContent(not_null<Window::Controller * > window)324 void IntroWidget::showContent(not_null<Window::Controller*> window) {
325 	setInnerWidget(CreateIntroSettings(_scroll, window));
326 
327 	_additionalScroll = 0;
328 	updateControlsGeometry();
329 	_topShadow->raise();
330 	_topShadow->finishAnimating();
331 }
332 
setInnerFocus()333 void IntroWidget::setInnerFocus() {
334 	setFocus();
335 }
336 
desiredHeightValue() const337 rpl::producer<int> IntroWidget::desiredHeightValue() const {
338 	using namespace rpl::mappers;
339 	return rpl::combine(
340 		_topBar->heightValue(),
341 		_innerWrap->entity()->desiredHeightValue(),
342 		_scrollTopSkip.value()
343 	) | rpl::map(_1 + _2 + _3);
344 }
345 
contentGeometry() const346 QRect IntroWidget::contentGeometry() const {
347 	return rect().marginsRemoved({ 0, _topBar->height(), 0, 0 });
348 }
349 
resizeEvent(QResizeEvent * e)350 void IntroWidget::resizeEvent(QResizeEvent *e) {
351 	updateControlsGeometry();
352 }
353 
keyPressEvent(QKeyEvent * e)354 void IntroWidget::keyPressEvent(QKeyEvent *e) {
355 	crl::on_main(this, [text = e->text()]{
356 		CodesFeedString(nullptr, text);
357 	});
358 	return RpWidget::keyPressEvent(e);
359 }
360 
applyAdditionalScroll(int additionalScroll)361 void IntroWidget::applyAdditionalScroll(int additionalScroll) {
362 	if (_innerWrap) {
363 		_innerWrap->setPadding({ 0, 0, 0, additionalScroll });
364 	}
365 }
366 
updateGeometry(QRect newGeometry,int additionalScroll)367 void IntroWidget::updateGeometry(QRect newGeometry, int additionalScroll) {
368 	auto scrollChanged = (_additionalScroll != additionalScroll);
369 	auto geometryChanged = (geometry() != newGeometry);
370 	auto shrinkingContent = (additionalScroll < _additionalScroll);
371 	_additionalScroll = additionalScroll;
372 
373 	if (geometryChanged) {
374 		if (shrinkingContent) {
375 			setGeometry(newGeometry);
376 		}
377 		if (scrollChanged) {
378 			applyAdditionalScroll(additionalScroll);
379 		}
380 		if (!shrinkingContent) {
381 			setGeometry(newGeometry);
382 		}
383 	} else if (scrollChanged) {
384 		applyAdditionalScroll(additionalScroll);
385 	}
386 }
387 
scrollTillBottom(int forHeight) const388 int IntroWidget::scrollTillBottom(int forHeight) const {
389 	auto scrollHeight = forHeight
390 		- _scrollTopSkip.current()
391 		- _topBar->height();
392 	auto scrollBottom = _scroll->scrollTop() + scrollHeight;
393 	auto desired = _innerDesiredHeight;
394 	return std::max(desired - scrollBottom, 0);
395 }
396 
scrollTillBottomChanges() const397 rpl::producer<int> IntroWidget::scrollTillBottomChanges() const {
398 	return _scrollTillBottomChanges.events();
399 }
400 
401 IntroWidget::~IntroWidget() = default;
402 
LayerWidget(QWidget *,not_null<Window::Controller * > window)403 LayerWidget::LayerWidget(QWidget*, not_null<Window::Controller*> window)
404 : _content(this, window) {
405 	setupHeightConsumers();
406 }
407 
setupHeightConsumers()408 void LayerWidget::setupHeightConsumers() {
409 	_content->scrollTillBottomChanges(
410 	) | rpl::filter([this] {
411 		return !_inResize;
412 	}) | rpl::start_with_next([this] {
413 		resizeToWidth(width());
414 	}, lifetime());
415 	_content->desiredHeightValue(
416 	) | rpl::start_with_next([this](int height) {
417 		accumulate_max(_desiredHeight, height);
418 		if (_content && !_inResize) {
419 			resizeToWidth(width());
420 		}
421 	}, lifetime());
422 }
423 
showFinished()424 void LayerWidget::showFinished() {
425 }
426 
parentResized()427 void LayerWidget::parentResized() {
428 	const auto parentSize = parentWidget()->size();
429 	const auto parentWidth = parentSize.width();
430 	const auto newWidth = (parentWidth < MinimalSupportedWidth())
431 		? parentWidth
432 		: qMin(
433 			parentWidth - 2 * st::infoMinimalLayerMargin,
434 			st::infoDesiredWidth);
435 	resizeToWidth(newWidth);
436 }
437 
MinimalSupportedWidth()438 int LayerWidget::MinimalSupportedWidth() {
439 	auto minimalMargins = 2 * st::infoMinimalLayerMargin;
440 	return st::infoMinimalWidth + minimalMargins;
441 }
442 
resizeGetHeight(int newWidth)443 int LayerWidget::resizeGetHeight(int newWidth) {
444 	if (!parentWidget() || !_content) {
445 		return 0;
446 	}
447 	_inResize = true;
448 	auto guard = gsl::finally([&] { _inResize = false; });
449 
450 	auto parentSize = parentWidget()->size();
451 	auto windowWidth = parentSize.width();
452 	auto windowHeight = parentSize.height();
453 	auto newLeft = (windowWidth - newWidth) / 2;
454 	if (!newLeft) {
455 		_content->updateGeometry({
456 			0,
457 			st::boxRadius,
458 			windowWidth,
459 			windowHeight - st::boxRadius }, 0);
460 		auto newGeometry = QRect(0, 0, windowWidth, windowHeight);
461 		if (newGeometry != geometry()) {
462 			_content->forceContentRepaint();
463 		}
464 		if (newGeometry.topLeft() != geometry().topLeft()) {
465 			move(newGeometry.topLeft());
466 		}
467 		_tillTop = _tillBottom = true;
468 		return windowHeight;
469 	}
470 	auto newTop = std::clamp(
471 		windowHeight / 24,
472 		st::infoLayerTopMinimal,
473 		st::infoLayerTopMaximal);
474 	auto newBottom = newTop;
475 	auto desiredHeight = st::boxRadius + _desiredHeight + st::boxRadius;
476 	accumulate_min(desiredHeight, windowHeight - newTop - newBottom);
477 
478 	// First resize content to new width and get the new desired height.
479 	auto contentLeft = 0;
480 	auto contentTop = st::boxRadius;
481 	auto contentBottom = st::boxRadius;
482 	auto contentWidth = newWidth;
483 	auto contentHeight = desiredHeight - contentTop - contentBottom;
484 	auto scrollTillBottom = _content->scrollTillBottom(contentHeight);
485 	auto additionalScroll = std::min(scrollTillBottom, newBottom);
486 
487 	desiredHeight += additionalScroll;
488 	contentHeight += additionalScroll;
489 	_tillTop = false;
490 	_tillBottom = (newTop + desiredHeight >= windowHeight);
491 	if (_tillBottom) {
492 		contentHeight += contentBottom;
493 		additionalScroll += contentBottom;
494 	}
495 	_content->updateGeometry({
496 		contentLeft,
497 		contentTop,
498 		contentWidth,
499 		contentHeight }, additionalScroll);
500 
501 	auto newGeometry = QRect(newLeft, newTop, newWidth, desiredHeight);
502 	if (newGeometry != geometry()) {
503 		_content->forceContentRepaint();
504 	}
505 	if (newGeometry.topLeft() != geometry().topLeft()) {
506 		move(newGeometry.topLeft());
507 	}
508 
509 	return desiredHeight;
510 }
511 
doSetInnerFocus()512 void LayerWidget::doSetInnerFocus() {
513 	_content->setInnerFocus();
514 }
515 
paintEvent(QPaintEvent * e)516 void LayerWidget::paintEvent(QPaintEvent *e) {
517 	Painter p(this);
518 
519 	auto clip = e->rect();
520 	auto r = st::boxRadius;
521 	auto parts = RectPart::None | 0;
522 	if (!_tillTop && clip.intersects({ 0, 0, width(), r })) {
523 		parts |= RectPart::FullTop;
524 	}
525 	if (!_tillBottom && clip.intersects({ 0, height() - r, width(), r })) {
526 		parts |= RectPart::FullBottom;
527 	}
528 	if (parts) {
529 		Ui::FillRoundRect(
530 			p,
531 			rect(),
532 			st::boxBg,
533 			Ui::BoxCorners,
534 			nullptr,
535 			parts);
536 	}
537 	if (_tillTop) {
538 		p.fillRect(0, 0, width(), r, st::boxBg);
539 	}
540 }
541 
542 } // namespace Settings
543