Add slide animations in CreatePollBox.
This commit is contained in:
		
							parent
							
								
									6f176803d4
								
							
						
					
					
						commit
						f291e365e5
					
				
					 2 changed files with 80 additions and 23 deletions
				
			
		| 
						 | 
					@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
#include "data/data_poll.h"
 | 
					#include "data/data_poll.h"
 | 
				
			||||||
#include "ui/toast/toast.h"
 | 
					#include "ui/toast/toast.h"
 | 
				
			||||||
#include "ui/wrap/vertical_layout.h"
 | 
					#include "ui/wrap/vertical_layout.h"
 | 
				
			||||||
 | 
					#include "ui/wrap/slide_wrap.h"
 | 
				
			||||||
#include "ui/widgets/input_fields.h"
 | 
					#include "ui/widgets/input_fields.h"
 | 
				
			||||||
#include "ui/widgets/shadow.h"
 | 
					#include "ui/widgets/shadow.h"
 | 
				
			||||||
#include "ui/widgets/labels.h"
 | 
					#include "ui/widgets/labels.h"
 | 
				
			||||||
| 
						 | 
					@ -56,6 +57,9 @@ private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void toggleRemoveAlways(bool toggled);
 | 
							void toggleRemoveAlways(bool toggled);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void show(anim::type animated);
 | 
				
			||||||
 | 
							void destroy(FnMut<void()> done);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		//[[nodisacrd]] bool hasShadow() const;
 | 
							//[[nodisacrd]] bool hasShadow() const;
 | 
				
			||||||
		//void destroyShadow();
 | 
							//void destroyShadow();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -75,6 +79,21 @@ private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		[[nodiscard]] rpl::producer<Qt::MouseButton> removeClicks() const;
 | 
							[[nodiscard]] rpl::producer<Qt::MouseButton> removeClicks() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							inline bool operator<(const Option &other) const {
 | 
				
			||||||
 | 
								return field() < other.field();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							friend inline bool operator<(
 | 
				
			||||||
 | 
									const Option &option,
 | 
				
			||||||
 | 
									Ui::InputField *field) {
 | 
				
			||||||
 | 
								return option.field() < field;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							friend inline bool operator<(
 | 
				
			||||||
 | 
									Ui::InputField *field,
 | 
				
			||||||
 | 
									const Option &option) {
 | 
				
			||||||
 | 
								return field < option.field();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		Option() = default;
 | 
							Option() = default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -82,7 +101,7 @@ private:
 | 
				
			||||||
		void createRemove();
 | 
							void createRemove();
 | 
				
			||||||
		void createWarning();
 | 
							void createWarning();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		base::unique_qptr<Ui::InputField> _field;
 | 
							base::unique_qptr<Ui::SlideWrap<Ui::InputField>> _field;
 | 
				
			||||||
		base::unique_qptr<Ui::PlainShadow> _shadow;
 | 
							base::unique_qptr<Ui::PlainShadow> _shadow;
 | 
				
			||||||
		base::unique_qptr<Ui::CrossButton> _remove;
 | 
							base::unique_qptr<Ui::CrossButton> _remove;
 | 
				
			||||||
		rpl::variable<bool> *_removeAlways = nullptr;
 | 
							rpl::variable<bool> *_removeAlways = nullptr;
 | 
				
			||||||
| 
						 | 
					@ -97,12 +116,15 @@ private:
 | 
				
			||||||
	void checkLastOption();
 | 
						void checkLastOption();
 | 
				
			||||||
	void validateState();
 | 
						void validateState();
 | 
				
			||||||
	void fixAfterErase();
 | 
						void fixAfterErase();
 | 
				
			||||||
 | 
						void destroy(Option &&option);
 | 
				
			||||||
 | 
						void removeDestroyed(not_null<Ui::InputField*> field);
 | 
				
			||||||
	int findField(not_null<Ui::InputField*> field) const;
 | 
						int findField(not_null<Ui::InputField*> field) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	not_null<QWidget*> _outer;
 | 
						not_null<QWidget*> _outer;
 | 
				
			||||||
	not_null<Ui::VerticalLayout*> _container;
 | 
						not_null<Ui::VerticalLayout*> _container;
 | 
				
			||||||
	int _position = 0;
 | 
						int _position = 0;
 | 
				
			||||||
	std::vector<Option> _list;
 | 
						std::vector<Option> _list;
 | 
				
			||||||
 | 
						std::set<Option, std::less<>> _destroyed;
 | 
				
			||||||
	rpl::variable<bool> _valid = false;
 | 
						rpl::variable<bool> _valid = false;
 | 
				
			||||||
	rpl::variable<int> _usedCount = 0;
 | 
						rpl::variable<int> _usedCount = 0;
 | 
				
			||||||
	rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
 | 
						rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
 | 
				
			||||||
| 
						 | 
					@ -157,12 +179,14 @@ Options::Option Options::Option::Create(
 | 
				
			||||||
	auto result = Option();
 | 
						auto result = Option();
 | 
				
			||||||
	const auto field = container->insert(
 | 
						const auto field = container->insert(
 | 
				
			||||||
		position,
 | 
							position,
 | 
				
			||||||
		object_ptr<Ui::InputField>(
 | 
							object_ptr<Ui::SlideWrap<Ui::InputField>>(
 | 
				
			||||||
			container,
 | 
								container,
 | 
				
			||||||
			st::createPollOptionField,
 | 
								object_ptr<Ui::InputField>(
 | 
				
			||||||
			langFactory(lng_polls_create_option_add)));
 | 
									container,
 | 
				
			||||||
	InitField(outer, field);
 | 
									st::createPollOptionField,
 | 
				
			||||||
	field->setMaxLength(kOptionLimit + kErrorLimit);
 | 
									langFactory(lng_polls_create_option_add))));
 | 
				
			||||||
 | 
						InitField(outer, field->entity());
 | 
				
			||||||
 | 
						field->entity()->setMaxLength(kOptionLimit + kErrorLimit);
 | 
				
			||||||
	result._field.reset(field);
 | 
						result._field.reset(field);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	result.createShadow();
 | 
						result.createShadow();
 | 
				
			||||||
| 
						 | 
					@ -181,9 +205,9 @@ void Options::Option::createShadow() {
 | 
				
			||||||
	if (_shadow) {
 | 
						if (_shadow) {
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	const auto value = Ui::CreateChild<Ui::PlainShadow>(_field.get());
 | 
						const auto value = Ui::CreateChild<Ui::PlainShadow>(field().get());
 | 
				
			||||||
	value->show();
 | 
						value->show();
 | 
				
			||||||
	_field->sizeValue(
 | 
						field()->sizeValue(
 | 
				
			||||||
	) | rpl::start_with_next([=](QSize size) {
 | 
						) | rpl::start_with_next([=](QSize size) {
 | 
				
			||||||
		const auto left = st::createPollFieldPadding.left();
 | 
							const auto left = st::createPollFieldPadding.left();
 | 
				
			||||||
		value->setGeometry(
 | 
							value->setGeometry(
 | 
				
			||||||
| 
						 | 
					@ -202,11 +226,11 @@ void Options::Option::createShadow() {
 | 
				
			||||||
void Options::Option::createRemove() {
 | 
					void Options::Option::createRemove() {
 | 
				
			||||||
	using namespace rpl::mappers;
 | 
						using namespace rpl::mappers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const auto field = _field.get();
 | 
						const auto field = this->field();
 | 
				
			||||||
	auto &lifetime = field->lifetime();
 | 
						auto &lifetime = field->lifetime();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const auto remove = Ui::CreateChild<Ui::CrossButton>(
 | 
						const auto remove = Ui::CreateChild<Ui::CrossButton>(
 | 
				
			||||||
		field,
 | 
							field.get(),
 | 
				
			||||||
		st::createPollOptionRemove);
 | 
							st::createPollOptionRemove);
 | 
				
			||||||
	remove->hide(anim::type::instant);
 | 
						remove->hide(anim::type::instant);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -239,7 +263,7 @@ void Options::Option::createRemove() {
 | 
				
			||||||
void Options::Option::createWarning() {
 | 
					void Options::Option::createWarning() {
 | 
				
			||||||
	using namespace rpl::mappers;
 | 
						using namespace rpl::mappers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const auto field = _field.get();
 | 
						const auto field = this->field();
 | 
				
			||||||
	const auto warning = CreateWarningLabel(
 | 
						const auto warning = CreateWarningLabel(
 | 
				
			||||||
		field,
 | 
							field,
 | 
				
			||||||
		field,
 | 
							field,
 | 
				
			||||||
| 
						 | 
					@ -261,31 +285,31 @@ void Options::Option::createWarning() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool Options::Option::isEmpty() const {
 | 
					bool Options::Option::isEmpty() const {
 | 
				
			||||||
	return _field->getLastText().trimmed().isEmpty();
 | 
						return field()->getLastText().trimmed().isEmpty();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool Options::Option::isGood() const {
 | 
					bool Options::Option::isGood() const {
 | 
				
			||||||
	return !_field->getLastText().trimmed().isEmpty() && !isTooLong();
 | 
						return !field()->getLastText().trimmed().isEmpty() && !isTooLong();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool Options::Option::isTooLong() const {
 | 
					bool Options::Option::isTooLong() const {
 | 
				
			||||||
	return (_field->getLastText().size() > kOptionLimit);
 | 
						return (field()->getLastText().size() > kOptionLimit);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool Options::Option::hasFocus() const {
 | 
					bool Options::Option::hasFocus() const {
 | 
				
			||||||
	return _field->hasFocus();
 | 
						return field()->hasFocus();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Options::Option::setFocus() const {
 | 
					void Options::Option::setFocus() const {
 | 
				
			||||||
	FocusAtEnd(_field);
 | 
						FocusAtEnd(field());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Options::Option::clearValue() {
 | 
					void Options::Option::clearValue() {
 | 
				
			||||||
	_field->setText(QString());
 | 
						field()->setText(QString());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Options::Option::setPlaceholder() const {
 | 
					void Options::Option::setPlaceholder() const {
 | 
				
			||||||
	_field->setPlaceholder(langFactory(lng_polls_create_option_add));
 | 
						field()->setPlaceholder(langFactory(lng_polls_create_option_add));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Options::Option::toggleRemoveAlways(bool toggled) {
 | 
					void Options::Option::toggleRemoveAlways(bool toggled) {
 | 
				
			||||||
| 
						 | 
					@ -293,16 +317,16 @@ void Options::Option::toggleRemoveAlways(bool toggled) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
not_null<Ui::InputField*> Options::Option::field() const {
 | 
					not_null<Ui::InputField*> Options::Option::field() const {
 | 
				
			||||||
	return _field.get();
 | 
						return _field->entity();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Options::Option::removePlaceholder() const {
 | 
					void Options::Option::removePlaceholder() const {
 | 
				
			||||||
	_field->setPlaceholder(nullptr);
 | 
						field()->setPlaceholder(nullptr);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PollAnswer Options::Option::toPollAnswer(char id) const {
 | 
					PollAnswer Options::Option::toPollAnswer(char id) const {
 | 
				
			||||||
	return PollAnswer{
 | 
						return PollAnswer{
 | 
				
			||||||
		_field->getLastText().trimmed(),
 | 
							field()->getLastText().trimmed(),
 | 
				
			||||||
		QByteArray(1, id)
 | 
							QByteArray(1, id)
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -344,6 +368,23 @@ rpl::producer<> Options::backspaceInFront() const {
 | 
				
			||||||
	return _backspaceInFront.events();
 | 
						return _backspaceInFront.events();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Options::Option::show(anim::type animated) {
 | 
				
			||||||
 | 
						_field->hide(anim::type::instant);
 | 
				
			||||||
 | 
						_field->show(animated);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Options::Option::destroy(FnMut<void()> done) {
 | 
				
			||||||
 | 
						if (anim::Disabled() || _field->isHidden()) {
 | 
				
			||||||
 | 
							Ui::PostponeCall(std::move(done));
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_field->hide(anim::type::normal);
 | 
				
			||||||
 | 
						App::CallDelayed(
 | 
				
			||||||
 | 
							st::slideWrapDuration * 2,
 | 
				
			||||||
 | 
							_field.get(),
 | 
				
			||||||
 | 
							std::move(done));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::vector<PollAnswer> Options::toPollAnswers() const {
 | 
					std::vector<PollAnswer> Options::toPollAnswers() const {
 | 
				
			||||||
	auto result = std::vector<PollAnswer>();
 | 
						auto result = std::vector<PollAnswer>();
 | 
				
			||||||
	result.reserve(_list.size());
 | 
						result.reserve(_list.size());
 | 
				
			||||||
| 
						 | 
					@ -405,10 +446,19 @@ void Options::removeEmptyTail() {
 | 
				
			||||||
	if (focusLast) {
 | 
						if (focusLast) {
 | 
				
			||||||
		emptyItem->setFocus();
 | 
							emptyItem->setFocus();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						for (auto i = emptyItem + 1; i != end; ++i) {
 | 
				
			||||||
 | 
							destroy(std::move(*i));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	_list.erase(emptyItem + 1, end);
 | 
						_list.erase(emptyItem + 1, end);
 | 
				
			||||||
	fixAfterErase();
 | 
						fixAfterErase();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Options::destroy(Option &&option) {
 | 
				
			||||||
 | 
						const auto field = option.field();
 | 
				
			||||||
 | 
						option.destroy([=] { removeDestroyed(field); });
 | 
				
			||||||
 | 
						_destroyed.emplace(std::move(option));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Options::fixAfterErase() {
 | 
					void Options::fixAfterErase() {
 | 
				
			||||||
	Expects(!_list.empty());
 | 
						Expects(!_list.empty());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -434,7 +484,7 @@ void Options::addEmptyOption() {
 | 
				
			||||||
	_list.push_back(Option::Create(
 | 
						_list.push_back(Option::Create(
 | 
				
			||||||
		_outer,
 | 
							_outer,
 | 
				
			||||||
		_container,
 | 
							_container,
 | 
				
			||||||
		_position + _list.size()));
 | 
							_position + _list.size() + _destroyed.size()));
 | 
				
			||||||
	const auto field = _list.back().field();
 | 
						const auto field = _list.back().field();
 | 
				
			||||||
	QObject::connect(field, &Ui::InputField::submitted, [=] {
 | 
						QObject::connect(field, &Ui::InputField::submitted, [=] {
 | 
				
			||||||
		const auto index = findField(field);
 | 
							const auto index = findField(field);
 | 
				
			||||||
| 
						 | 
					@ -482,15 +532,23 @@ void Options::addEmptyOption() {
 | 
				
			||||||
			if (item->hasFocus()) {
 | 
								if (item->hasFocus()) {
 | 
				
			||||||
				(item + 1)->setFocus();
 | 
									(item + 1)->setFocus();
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								destroy(std::move(*item));
 | 
				
			||||||
			_list.erase(item);
 | 
								_list.erase(item);
 | 
				
			||||||
			fixAfterErase();
 | 
								fixAfterErase();
 | 
				
			||||||
			validateState();
 | 
								validateState();
 | 
				
			||||||
		}));
 | 
							}));
 | 
				
			||||||
	}, field->lifetime());
 | 
						}, field->lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_list.back().show((_list.size() == 1)
 | 
				
			||||||
 | 
							? anim::type::instant
 | 
				
			||||||
 | 
							: anim::type::normal);
 | 
				
			||||||
	//fixShadows();
 | 
						//fixShadows();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Options::removeDestroyed(not_null<Ui::InputField*> field) {
 | 
				
			||||||
 | 
						_destroyed.erase(_destroyed.find(field));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Options::validateState() {
 | 
					void Options::validateState() {
 | 
				
			||||||
	checkLastOption();
 | 
						checkLastOption();
 | 
				
			||||||
	_valid = (ranges::count_if(_list, &Option::isGood) > 1)
 | 
						_valid = (ranges::count_if(_list, &Option::isGood) > 1)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -111,7 +111,6 @@ private:
 | 
				
			||||||
	void resetAnswersAnimation() const;
 | 
						void resetAnswersAnimation() const;
 | 
				
			||||||
	void step_radial(TimeMs ms, bool timer);
 | 
						void step_radial(TimeMs ms, bool timer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void checkPollResultsReload(TimeMs ms) const;
 | 
					 | 
				
			||||||
	void toggleRipple(Answer &answer, bool pressed);
 | 
						void toggleRipple(Answer &answer, bool pressed);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	not_null<PollData*> _poll;
 | 
						not_null<PollData*> _poll;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue