From 90234cb7a0b2ecbf67e71289b09755d72b02a39c Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 9 Dec 2016 21:56:01 +0300 Subject: [PATCH] Closed beta 10019014: New input fields design and animations. --- Telegram/Resources/basic.style | 9 - Telegram/Resources/langs/lang.strings | 2 + Telegram/Resources/winrc/Telegram.rc | 8 +- Telegram/Resources/winrc/Updater.rc | 8 +- Telegram/SourceFiles/boxes/addcontactbox.cpp | 16 +- Telegram/SourceFiles/boxes/boxes.style | 63 +- Telegram/SourceFiles/boxes/connectionbox.cpp | 11 +- Telegram/SourceFiles/boxes/passcodebox.cpp | 26 +- Telegram/SourceFiles/boxes/report_box.cpp | 2 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 8 +- Telegram/SourceFiles/boxes/usernamebox.cpp | 13 +- Telegram/SourceFiles/boxes/usernamebox.h | 3 +- Telegram/SourceFiles/core/lambda.h | 43 +- Telegram/SourceFiles/core/utils.cpp | 3 +- Telegram/SourceFiles/core/version.h | 2 +- Telegram/SourceFiles/facades.cpp | 3 + Telegram/SourceFiles/facades.h | 22 + Telegram/SourceFiles/history/history.style | 19 + Telegram/SourceFiles/historywidget.cpp | 88 +- Telegram/SourceFiles/historywidget.h | 4 +- Telegram/SourceFiles/intro/intro.style | 40 +- Telegram/SourceFiles/intro/introcode.cpp | 21 +- Telegram/SourceFiles/intro/introphone.cpp | 30 +- Telegram/SourceFiles/intro/introphone.h | 2 - Telegram/SourceFiles/intro/intropwdcheck.cpp | 27 +- Telegram/SourceFiles/intro/introsignup.cpp | 43 +- Telegram/SourceFiles/layerwidget.cpp | 1 + .../SourceFiles/overview/overview_layout.cpp | 16 +- .../SourceFiles/overview/overview_layout.h | 7 +- Telegram/SourceFiles/overviewwidget.cpp | 33 +- Telegram/SourceFiles/overviewwidget.h | 3 +- Telegram/SourceFiles/passcodewidget.cpp | 5 +- Telegram/SourceFiles/profile/profile.style | 16 +- .../profile/profile_block_group_members.cpp | 3 +- .../profile/profile_block_group_members.h | 2 +- .../profile/profile_block_peer_list.cpp | 23 +- .../profile/profile_block_peer_list.h | 5 +- .../SourceFiles/profile/profile_cover.cpp | 8 +- .../SourceFiles/settings/settings_cover.cpp | 2 +- .../settings/settings_scale_widget.cpp | 4 +- Telegram/SourceFiles/structs.cpp | 16 +- Telegram/SourceFiles/ui/countryinput.cpp | 37 +- Telegram/SourceFiles/ui/countryinput.h | 1 + .../ui/effects/widget_fade_wrap.cpp | 7 +- .../SourceFiles/ui/widgets/input_fields.cpp | 947 ++++++++++-------- .../SourceFiles/ui/widgets/input_fields.h | 261 ++--- .../SourceFiles/ui/widgets/multi_select.cpp | 2 +- Telegram/SourceFiles/ui/widgets/widgets.style | 85 +- .../window/notifications_manager_default.cpp | 2 +- Telegram/SourceFiles/window/window.style | 4 +- Telegram/build/version | 2 +- 51 files changed, 1068 insertions(+), 940 deletions(-) diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index f10548b37..3aefbf874 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -394,15 +394,6 @@ radialPeriod: 3000; radialFg: #ffffff; radialBg: #00000056; -downloadPathSkip: 10px; - -usernamePadding: margins(23px, 22px, 21px, 12px); -usernameSkip: 49px; -usernameTextStyle: TextStyle(defaultTextStyle) { - lineHeight: 20px; -} -usernameDefaultFg: #777777; - youtubeIcon: icon { { "media_youtube_play_bg", #e83131c8 }, { "media_youtube_play", #ffffff, point(24px, 12px) }, diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index cd345a1e6..e9a46b775 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -154,6 +154,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_phone_notreg" = "If you don't have a Telegram account yet,\nplease [b]sign up[/b] with {link_start}Android / iPhone{link_end} or {signup_start}here{signup_end}"; "lng_country_code" = "Country Code"; "lng_bad_country_code" = "Invalid Country Code"; +"lng_country_fake_ph" = "Your country"; "lng_country_ph" = "Search"; "lng_country_done" = "Done"; "lng_country_none" = "Country not found"; @@ -351,6 +352,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_passcode_wrong" = "Wrong passcode"; "lng_passcode_is_same" = "Passcode was not changed"; "lng_passcode_enter" = "Enter your local passcode"; +"lng_passcode_ph" = "Your passcode"; "lng_passcode_submit" = "Submit"; "lng_passcode_logout" = "Log out"; "lng_passcode_need_unblock" = "You need to unlock me first."; diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index c1ad6d24d..c04a8c9e5 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,10,19,13 - PRODUCTVERSION 0,10,19,13 + FILEVERSION 0,10,19,14 + PRODUCTVERSION 0,10,19,14 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.10.19.13" + VALUE "FileVersion", "0.10.19.14" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.10.19.13" + VALUE "ProductVersion", "0.10.19.14" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 1caac85bc..6bd1cf42f 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,10,19,13 - PRODUCTVERSION 0,10,19,13 + FILEVERSION 0,10,19,14 + PRODUCTVERSION 0,10,19,14 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,10 +43,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Updater" - VALUE "FileVersion", "0.10.19.13" + VALUE "FileVersion", "0.10.19.14" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.10.19.13" + VALUE "ProductVersion", "0.10.19.14" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index 988375ac1..54000b93a 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -239,9 +239,7 @@ void AddContactBox::onRetry() { _retry->hide(); resizeEvent(0); _first->setText(QString()); - _first->updatePlaceholder(); _last->setText(QString()); - _last->updatePlaceholder(); _phone->clearText(); _phone->setDisabled(false); _first->setFocus(); @@ -274,13 +272,11 @@ GroupInfoBox::GroupInfoBox(CreatingGroupType creating, bool fromTypeChoose) : Ab connect(_next, SIGNAL(clicked()), this, SLOT(onNext())); connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - _photo->setClickedCallback([this] { - App::CallDelayed(st::defaultActiveButton.ripple.hideDuration, base::lambda_guarded(this, [this] { - auto imgExtensions = cImgExtensions(); - auto filter = qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + filedialogAllFilesFilter(); - _setPhotoFileQueryId = FileDialog::queryReadFile(lang(lng_choose_image), filter); - })); - }); + _photo->setClickedCallback(App::LambdaDelayed(st::defaultActiveButton.ripple.hideDuration, this, [this] { + auto imgExtensions = cImgExtensions(); + auto filter = qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + filedialogAllFilesFilter(); + _setPhotoFileQueryId = FileDialog::queryReadFile(lang(lng_choose_image), filter); + })); subscribe(FileDialog::QueryDone(), [this](const FileDialog::QueryUpdate &update) { notifyFileQueryUpdated(update); }); @@ -433,7 +429,7 @@ SetupChannelBox::SetupChannelBox(ChannelData *channel, bool existing) : Abstract , _aboutPublicWidth(width() - st::boxPadding.left() - st::boxButtonPadding.right() - st::newGroupPadding.left() - st::defaultBoxCheckbox.textPosition.x()) , _aboutPublic(st::normalFont, lang(channel->isMegagroup() ? lng_create_public_group_about : lng_create_public_channel_about), _defaultOptions, _aboutPublicWidth) , _aboutPrivate(st::normalFont, lang(channel->isMegagroup() ? lng_create_private_group_about : lng_create_private_channel_about), _defaultOptions, _aboutPublicWidth) -, _link(this, st::defaultInputField, QString(), channel->username, true) +, _link(this, st::setupChannelLink, QString(), channel->username, true) , _linkOver(false) , _save(this, lang(lng_settings_save), st::defaultBoxButton) , _skip(this, lang(existing ? lng_cancel : lng_create_group_skip), st::cancelBoxButton) { diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index b38e20780..e45b40eb8 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -97,6 +97,7 @@ boxLinkButton: LinkButton { } boxOptionListPadding: margins(2px, 20px, 2px, 2px); +boxOptionInputSkip: 6px; boxVerticalMargin: 10px; boxWidth: 320px; @@ -146,7 +147,7 @@ boxPhotoTitleFont: font(16px semibold); boxPhotoTitlePosition: point(28px, 26px); boxPhotoPadding: margins(28px, 28px, 28px, 0px); boxPhotoCompressedSkip: 20px; -boxPhotoCaptionSkip: 22px; +boxPhotoCaptionSkip: 8px; boxPhotoTextFg: #808080; cropPointSize: 10px; @@ -202,7 +203,7 @@ aboutRevokePublicLabel: FlatLabel(defaultFlatLabel) { contactUserIcon: icon {{ "add_contact_user", #999999 }}; contactPhoneIcon: icon {{ "add_contact_phone", #999999 }}; -contactIconTop: 10px; +contactIconTop: 28px; contactsAddIconAbove: icon {{ "contacts_add", activeButtonFg, point(18px, 18px) }}; contactsAdd: TwoIconButton { @@ -222,8 +223,8 @@ contactsAdd: TwoIconButton { } contactsAddPosition: point(14px, 8px); -contactPadding: margins(49px, 22px, 0px, 6px); -contactSkip: 13px; +contactPadding: margins(49px, 2px, 0px, 6px); +contactSkip: 6px; contactPhoneSkip: 30px; contactsPhotoSize: 42px; @@ -283,13 +284,16 @@ contactsMultiSelect: MultiSelect { placeholderFg: #999999; placeholderFgActive: #aaaaaa; + placeholderFgError: #aaaaaa; placeholderMargins: margins(2px, 0px, 2px, 0px); + placeholderScale: 0.; + placeholderFont: normalFont; border: 0px; borderActive: 0px; borderError: 0px; - height: 32px; + heightMin: 32px; font: normalFont; } @@ -406,20 +410,23 @@ sessionTerminateAllButton: LinkButton(boxLinkButton) { passcodeHeaderFont: font(19px); passcodeHeaderHeight: 80px; -passcodeInput: introPhone; +passcodeInput: InputField(introPhone) { + textMargins: margins(1px, 27px, 1px, 6px); +} passcodeSubmit: RoundButton(introNextButton) { width: 225px; } passcodeSubmitSkip: 40px; -passcodePadding: margins(0px, 22px, 0px, 3px); -passcodeSkip: 31px; +passcodePadding: margins(0px, 6px, 0px, 13px); +passcodeTextLine: 28px; +passcodeSkip: 21px; newGroupAboutFg: #808080; newGroupPadding: margins(4px, 6px, 4px, 3px); -newGroupSkip: 17px; +newGroupSkip: 27px; newGroupInfoPadding: margins(0px, -4px, 0px, 1px); -newGroupLinkPadding: margins(4px, 27px, 4px, 12px); +newGroupLinkPadding: margins(4px, 27px, 4px, 21px); newGroupLinkTop: 3px; newGroupLinkFont: font(16px); @@ -428,12 +435,17 @@ newGroupPhotoIcon: icon {{ "new_chat_photo", #ffffff }}; newGroupPhotoIconPosition: point(23px, 25px); newGroupPhotoDuration: 150; -newGroupNamePosition: point(27px, 20px); +newGroupNamePosition: point(27px, 5px); -newGroupDescriptionPadding: margins(0px, 23px, 0px, 14px); -newGroupDescription: InputArea(defaultInputArea) { - textMargins: margins(1px, 6px, 1px, 4px); - heightMax: 115px; +newGroupDescriptionPadding: margins(0px, 13px, 0px, 4px); +newGroupDescription: InputField(defaultInputField) { + textMargins: margins(1px, 26px, 1px, 4px); + heightMax: 135px; +} + +setupChannelLink: InputField(defaultInputField) { + textMargins: margins(0px, 6px, 0px, 4px); + heightMin: 32px; } newGroupPublicLinkPadding: margins(0px, 20px, 0px, 5px); @@ -463,14 +475,14 @@ aboutTextStyle: TextStyle(defaultTextStyle) { lineHeight: 22px; } -editTextArea: InputArea(defaultInputArea) { - textMargins: margins(1px, 6px, 1px, 4px); - heightMax: 256px; +editTextArea: InputField(defaultInputField) { + textMargins: margins(1px, 26px, 1px, 4px); + heightMax: 276px; } -confirmCaptionArea: InputArea(defaultInputArea) { - textMargins: margins(1px, 6px, 1px, 4px); - heightMax: 56px; +confirmCaptionArea: InputField(defaultInputField) { + textMargins: margins(1px, 26px, 1px, 4px); + heightMax: 78px; } confirmBg: windowBgOver; confirmMaxHeight: 245px; @@ -501,3 +513,12 @@ backgroundScroll: FlatScroll(boxScroll) { deltat: 10px; deltab: 0px; } + +usernamePadding: margins(23px, 6px, 21px, 12px); +usernameSkip: 49px; +usernameTextStyle: TextStyle(defaultTextStyle) { + lineHeight: 20px; +} +usernameDefaultFg: #777777; + +downloadPathSkip: 10px; diff --git a/Telegram/SourceFiles/boxes/connectionbox.cpp b/Telegram/SourceFiles/boxes/connectionbox.cpp index b7cb5447b..6e458050d 100644 --- a/Telegram/SourceFiles/boxes/connectionbox.cpp +++ b/Telegram/SourceFiles/boxes/connectionbox.cpp @@ -61,7 +61,7 @@ ConnectionBox::ConnectionBox() : AbstractBox(st::boxWidth, lang(lng_connection_h void ConnectionBox::updateControlsVisibility() { int32 h = titleHeight() + st::boxOptionListPadding.top() + _autoRadio->heightNoMargins() + st::boxOptionListPadding.top() + _httpProxyRadio->heightNoMargins() + st::boxOptionListPadding.top() + _tcpProxyRadio->heightNoMargins() + st::boxOptionListPadding.top() + st::connectionIPv6Skip + _tryIPv6->heightNoMargins() + st::boxOptionListPadding.bottom() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom(); if (_httpProxyRadio->checked() || _tcpProxyRadio->checked()) { - h += 2 * st::boxOptionListPadding.top() + 2 * _hostInput->height(); + h += 2 * st::boxOptionInputSkip + 2 * _hostInput->height(); _hostInput->show(); _portInput->show(); _userInput->show(); @@ -89,19 +89,19 @@ void ConnectionBox::resizeEvent(QResizeEvent *e) { int32 inputy = 0; if (_httpProxyRadio->checked()) { - inputy = _httpProxyRadio->bottomNoMargins() + st::boxOptionListPadding.top(); - _tcpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), inputy + st::boxOptionListPadding.top() + 2 * _hostInput->height() + st::boxOptionListPadding.top()); + inputy = _httpProxyRadio->bottomNoMargins() + st::boxOptionInputSkip; + _tcpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), inputy + st::boxOptionInputSkip + 2 * _hostInput->height() + st::boxOptionListPadding.top()); } else { _tcpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _httpProxyRadio->bottomNoMargins() + st::boxOptionListPadding.top()); if (_tcpProxyRadio->checked()) { - inputy = _tcpProxyRadio->bottomNoMargins() + st::boxOptionListPadding.top(); + inputy = _tcpProxyRadio->bottomNoMargins() + st::boxOptionInputSkip; } } if (inputy) { _hostInput->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultBoxCheckbox.textPosition.x() - st::defaultInputField.textMargins.left(), inputy); _portInput->moveToRight(st::boxPadding.right(), inputy); - _userInput->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultBoxCheckbox.textPosition.x() - st::defaultInputField.textMargins.left(), _hostInput->y() + _hostInput->height() + st::boxOptionListPadding.top()); + _userInput->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultBoxCheckbox.textPosition.x() - st::defaultInputField.textMargins.left(), _hostInput->y() + _hostInput->height() + st::boxOptionInputSkip); _passwordInput->moveToRight(st::boxPadding.right(), _userInput->y()); } @@ -120,7 +120,6 @@ void ConnectionBox::onChange() { _hostInput->setFocus(); if (_httpProxyRadio->checked() && !_portInput->getLastText().toInt()) { _portInput->setText(qsl("80")); - _portInput->updatePlaceholder(); } } update(); diff --git a/Telegram/SourceFiles/boxes/passcodebox.cpp b/Telegram/SourceFiles/boxes/passcodebox.cpp index a47d57480..41e23f151 100644 --- a/Telegram/SourceFiles/boxes/passcodebox.cpp +++ b/Telegram/SourceFiles/boxes/passcodebox.cpp @@ -75,13 +75,13 @@ void PasscodeBox::init() { if (_turningOff) { _oldPasscode->show(); setTitleText(lang(_cloudPwd ? lng_cloud_password_remove : lng_passcode_remove)); - setMaxHeight(titleHeight() + st::passcodePadding.top() + _oldPasscode->height() + st::passcodeSkip + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeSkip : 0) + _aboutHeight + st::passcodePadding.bottom() + st::boxButtonPadding.top() + _saveButton->height() + st::boxButtonPadding.bottom()); + setMaxHeight(titleHeight() + st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + _aboutHeight + st::passcodePadding.bottom() + st::boxButtonPadding.top() + _saveButton->height() + st::boxButtonPadding.bottom()); } else { bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode(); if (has) { _oldPasscode->show(); setTitleText(lang(_cloudPwd ? lng_cloud_password_change : lng_passcode_change)); - setMaxHeight(titleHeight() + st::passcodePadding.top() + _oldPasscode->height() + st::passcodeSkip + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeSkip : 0) + _newPasscode->height() + st::contactSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::contactSkip : 0) + _aboutHeight + st::passcodePadding.bottom() + st::boxButtonPadding.top() + _saveButton->height() + st::boxButtonPadding.bottom()); + setMaxHeight(titleHeight() + st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + _newPasscode->height() + st::contactSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::contactSkip : 0) + _aboutHeight + st::passcodePadding.bottom() + st::boxButtonPadding.top() + _saveButton->height() + st::boxButtonPadding.bottom()); } else { _oldPasscode->hide(); setTitleText(lang(_cloudPwd ? lng_cloud_password_create : lng_passcode_create)); @@ -160,27 +160,27 @@ void PasscodeBox::paintEvent(QPaintEvent *e) { textstyleSet(&st::usernameTextStyle); int32 w = st::boxWidth - st::boxPadding.left() * 1.5; - int32 abouty = (_passwordHint->isHidden() ? (_reenterPasscode->isHidden() ? (_oldPasscode->y() + (_hasRecovery && !_hintText.isEmpty() ? st::passcodeSkip : 0)) : _reenterPasscode->y()) + st::passcodeSkip : _passwordHint->y() + st::contactSkip) + _oldPasscode->height(); + int32 abouty = (_passwordHint->isHidden() ? (_reenterPasscode->isHidden() ? (_oldPasscode->y() + (_hasRecovery && !_hintText.isEmpty() ? st::passcodeTextLine : 0)) : _reenterPasscode->y()) + st::passcodeSkip : _passwordHint->y() + st::contactSkip) + _oldPasscode->height() + st::passcodePadding.bottom(); p.setPen(st::boxTextFg); _about.drawLeft(p, st::boxPadding.left(), abouty, w, width()); if (!_hintText.isEmpty() && _oldError.isEmpty()) { - _hintText.drawLeftElided(p, st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + ((st::passcodeSkip - st::normalFont->height) / 2), w, width(), 1, style::al_topleft); + _hintText.drawLeftElided(p, st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + ((st::passcodeTextLine - st::normalFont->height) / 2), w, width(), 1, style::al_topleft); } if (!_oldError.isEmpty()) { p.setPen(st::boxTextFgError); - p.drawText(QRect(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height(), w, st::passcodeSkip), _oldError, style::al_left); + p.drawText(QRect(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height(), w, st::passcodeTextLine), _oldError, style::al_left); } if (!_newError.isEmpty()) { p.setPen(st::boxTextFgError); - p.drawText(QRect(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height(), w, st::passcodeSkip), _newError, style::al_left); + p.drawText(QRect(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height(), w, st::passcodeTextLine), _newError, style::al_left); } if (!_emailError.isEmpty()) { p.setPen(st::boxTextFgError); - p.drawText(QRect(st::boxPadding.left(), _recoverEmail->y() + _recoverEmail->height(), w, st::passcodeSkip), _emailError, style::al_left); + p.drawText(QRect(st::boxPadding.left(), _recoverEmail->y() + _recoverEmail->height(), w, st::passcodeTextLine), _emailError, style::al_left); } textstyleRestore(); @@ -192,7 +192,7 @@ void PasscodeBox::resizeEvent(QResizeEvent *e) { _oldPasscode->resize(w, _oldPasscode->height()); _oldPasscode->moveToLeft(st::boxPadding.left(), titleHeight() + st::passcodePadding.top()); _newPasscode->resize(w, _newPasscode->height()); - _newPasscode->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + ((_turningOff || has) ? (_oldPasscode->height() + st::passcodeSkip + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeSkip : 0)) : 0)); + _newPasscode->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + ((_turningOff || has) ? (_oldPasscode->height() + st::passcodeTextLine + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0)) : 0)); _reenterPasscode->resize(w, _reenterPasscode->height()); _reenterPasscode->moveToLeft(st::boxPadding.left(), _newPasscode->y() + _newPasscode->height() + st::contactSkip); _passwordHint->resize(w, _passwordHint->height()); @@ -201,7 +201,7 @@ void PasscodeBox::resizeEvent(QResizeEvent *e) { _recoverEmail->moveToLeft(st::boxPadding.left(), _passwordHint->y() + _passwordHint->height() + st::contactSkip + _aboutHeight + st::contactSkip); if (!_recover->isHidden()) { - _recover->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + (_hintText.isEmpty() ? ((st::passcodeSkip - _recover->height()) / 2) : st::passcodeSkip)); + _recover->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + (_hintText.isEmpty() ? ((st::passcodeTextLine - _recover->height()) / 2) : st::passcodeTextLine)); } _saveButton->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _saveButton->height()); @@ -455,7 +455,7 @@ RecoverBox::RecoverBox(const QString &pattern) : AbstractBox(st::boxWidth, lang( , _recoverCode(this, st::defaultInputField, lang(lng_signin_code)) { setBlockTitle(true); - setMaxHeight(titleHeight() + st::passcodePadding.top() + st::passcodeSkip + _recoverCode->height() + st::passcodeSkip + st::passcodePadding.bottom() + st::boxButtonPadding.top() + _saveButton->height() + st::boxButtonPadding.bottom()); + setMaxHeight(titleHeight() + st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine + _recoverCode->height() + st::passcodeTextLine + st::boxButtonPadding.top() + _saveButton->height() + st::boxButtonPadding.bottom()); connect(_saveButton, SIGNAL(clicked()), this, SLOT(onSubmit())); connect(_cancelButton, SIGNAL(clicked()), this, SLOT(onClose())); @@ -474,17 +474,17 @@ void RecoverBox::paintEvent(QPaintEvent *e) { p.setFont(st::normalFont); p.setPen(st::boxTextFg); int32 w = st::boxWidth - st::boxPadding.left() * 1.5; - p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() - st::passcodeSkip - st::passcodePadding.top(), w, st::passcodePadding.top() + st::passcodeSkip), _pattern, style::al_left); + p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() - st::passcodeTextLine - st::passcodePadding.top(), w, st::passcodePadding.top() + st::passcodeTextLine), _pattern, style::al_left); if (!_error.isEmpty()) { p.setPen(st::boxTextFgError); - p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height(), w, st::passcodeSkip), _error, style::al_left); + p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height(), w, st::passcodeTextLine), _error, style::al_left); } } void RecoverBox::resizeEvent(QResizeEvent *e) { _recoverCode->resize(st::boxWidth - st::boxPadding.left() - st::boxPadding.right(), _recoverCode->height()); - _recoverCode->moveToLeft(st::boxPadding.left(), titleHeight() + st::passcodePadding.top() + st::passcodeSkip); + _recoverCode->moveToLeft(st::boxPadding.left(), titleHeight() + st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine); _saveButton->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _saveButton->height()); _cancelButton->moveToRight(st::boxButtonPadding.right() + _saveButton->width() + st::boxButtonPadding.left(), _saveButton->y()); diff --git a/Telegram/SourceFiles/boxes/report_box.cpp b/Telegram/SourceFiles/boxes/report_box.cpp index 4a30c7049..7c582dd6a 100644 --- a/Telegram/SourceFiles/boxes/report_box.cpp +++ b/Telegram/SourceFiles/boxes/report_box.cpp @@ -71,7 +71,7 @@ void ReportBox::onChange() { if (!_reasonOtherText) { _reasonOtherText.create(this, st::profileReportReasonOther, lang(lng_report_reason_description)); _reasonOtherText->show(); - _reasonOtherText->setCtrlEnterSubmit(Ui::CtrlEnterSubmitBoth); + _reasonOtherText->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both); _reasonOtherText->setMaxLength(MaxPhotoCaption); _reasonOtherText->resize(width() - (st::boxPadding.left() + st::boxOptionListPadding.left() + st::boxPadding.right()), _reasonOtherText->height()); diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 5f6f32edd..57e2978ae 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -157,7 +157,7 @@ void SendFilesBox::setup() { } if (_caption) { _caption->setMaxLength(MaxPhotoCaption); - _caption->setCtrlEnterSubmit(Ui::CtrlEnterSubmitBoth); + _caption->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both); connect(_caption, SIGNAL(resized()), this, SLOT(onCaptionResized())); connect(_caption, SIGNAL(submitted(bool)), this, SLOT(onSend(bool))); connect(_caption, SIGNAL(cancelled()), this, SLOT(onClose())); @@ -484,13 +484,13 @@ EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth) if (_animated || _photo || _doc) { _field.create(this, st::confirmCaptionArea, lang(lng_photo_caption), caption); _field->setMaxLength(MaxPhotoCaption); - _field->setCtrlEnterSubmit(Ui::CtrlEnterSubmitBoth); + _field->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both); } else { auto original = msg->originalText(); QString text = textApplyEntities(original.text, original.entities); _field.create(this, st::editTextArea, lang(lng_photo_caption), text); // _field->setMaxLength(MaxMessageSize); // entities can make text in input field larger but still valid - _field->setCtrlEnterSubmit(cCtrlEnter() ? Ui::CtrlEnterSubmitCtrlEnter : Ui::CtrlEnterSubmitEnter); + _field->setCtrlEnterSubmit(cCtrlEnter() ? Ui::CtrlEnterSubmit::CtrlEnter : Ui::CtrlEnterSubmit::Enter); } updateBoxSize(); connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSave(bool))); @@ -547,7 +547,7 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } - + auto icon = &st::historyFileInPlay; icon->paintInCenter(p, inner); } diff --git a/Telegram/SourceFiles/boxes/usernamebox.cpp b/Telegram/SourceFiles/boxes/usernamebox.cpp index c67cbf123..4d5472fd1 100644 --- a/Telegram/SourceFiles/boxes/usernamebox.cpp +++ b/Telegram/SourceFiles/boxes/usernamebox.cpp @@ -29,13 +29,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/widgets/input_fields.h" #include "styles/style_boxes.h" -UsernameBox::UsernameBox() : AbstractBox(st::boxWidth, lang(lng_username_title)), -_save(this, lang(lng_settings_save), st::defaultBoxButton), -_cancel(this, lang(lng_cancel), st::cancelBoxButton), -_username(this, st::defaultInputField, qsl("@username"), App::self()->username, false), -_link(this, QString(), st::boxLinkButton), -_saveRequestId(0), _checkRequestId(0), -_about(st::boxWidth - st::usernamePadding.left()) { +UsernameBox::UsernameBox() : AbstractBox(st::boxWidth, lang(lng_username_title)) +, _save(this, lang(lng_settings_save), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) +, _username(this, st::defaultInputField, qsl("@username"), App::self()->username, false) +, _link(this, QString(), st::boxLinkButton) +, _about(st::boxWidth - st::usernamePadding.left()) { setBlockTitle(true); _goodText = App::self()->username.isEmpty() ? QString() : lang(lng_username_available); diff --git a/Telegram/SourceFiles/boxes/usernamebox.h b/Telegram/SourceFiles/boxes/usernamebox.h index f827996b3..e75ae0cab 100644 --- a/Telegram/SourceFiles/boxes/usernamebox.h +++ b/Telegram/SourceFiles/boxes/usernamebox.h @@ -63,7 +63,8 @@ private: ChildWidget _username; ChildWidget _link; - mtpRequestId _saveRequestId, _checkRequestId; + mtpRequestId _saveRequestId = 0; + mtpRequestId _checkRequestId = 0; QString _sentUsername, _checkUsername, _errorText, _goodText, _copiedTextLink; Text _about; diff --git a/Telegram/SourceFiles/core/lambda.h b/Telegram/SourceFiles/core/lambda.h index 21c55d0af..e7cafbb3f 100644 --- a/Telegram/SourceFiles/core/lambda.h +++ b/Telegram/SourceFiles/core/lambda.h @@ -424,11 +424,17 @@ public: using return_type = typename lambda_type::return_type; template - lambda_guard_data(PointersAndLambda&&... qobjectsAndLambda) : _lambda(init(_pointers, std_::forward(qobjectsAndLambda)...)) { + inline lambda_guard_data(PointersAndLambda&&... qobjectsAndLambda) : _lambda(init(_pointers, std_::forward(qobjectsAndLambda)...)) { + } + + inline lambda_guard_data(const lambda_guard_data &other) : _lambda(other._lambda) { + for (auto i = 0; i != N; ++i) { + _pointers[i] = other._pointers[i]; + } } template - inline return_type operator()(Args... args) const { + inline return_type operator()(Args&&... args) const { for (int i = 0; i != N; ++i) { if (!_pointers[i]) { return return_type(); @@ -458,17 +464,44 @@ public: using return_type = typename lambda_type::return_type; template - lambda_guard(PointersAndLambda&&... qobjectsAndLambda) : _data(std_::make_unique>(std_::forward(qobjectsAndLambda)...)) { + inline lambda_guard(PointersAndLambda&&... qobjectsAndLambda) : _data(std_::make_unique>(std_::forward(qobjectsAndLambda)...)) { static_assert(sizeof...(PointersAndLambda) == N + 1, "Wrong argument count!"); } + inline lambda_guard(const lambda_guard &&other) : _data(std_::move(other._data)) { + } + + inline lambda_guard(lambda_guard &&other) : _data(std_::move(other._data)) { + } + + inline lambda_guard &operator=(const lambda_guard &&other) { + _data = std_::move(other._data); + return *this; + } + + inline lambda_guard &operator=(lambda_guard &&other) { + _data = std_::move(other._data); + return *this; + } + template - inline return_type operator()(Args... args) const { + inline return_type operator()(Args&&... args) const { return (*_data)(std_::forward(args)...); } + bool isNull() const { + return !_data; + } + + lambda_guard clone() const { + return lambda_guard(*this); + } + private: - std_::unique_ptr> _data; + inline lambda_guard(const lambda_guard &other) : _data(std_::make_unique>(static_cast &>(*other._data))) { + } + + mutable std_::unique_ptr> _data; }; diff --git a/Telegram/SourceFiles/core/utils.cpp b/Telegram/SourceFiles/core/utils.cpp index 057d37604..7999b7a60 100644 --- a/Telegram/SourceFiles/core/utils.cpp +++ b/Telegram/SourceFiles/core/utils.cpp @@ -335,7 +335,8 @@ TimeMs getms(bool checked) { return ((msCount - _msStart) * _msFreq) + (checked ? _msAddToMsStart : 0LL); #else timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { + auto res = clock_gettime(CLOCK_MONOTONIC, &ts); + if (res != 0) { LOG(("Bad clock_gettime result: %1").arg(res)); return 0; } diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 15c81fa88..0b0bcddf1 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/utils.h" -#define BETA_VERSION_MACRO (10019013ULL) +#define BETA_VERSION_MACRO (10019014ULL) constexpr int AppVersion = 10020; constexpr str_const AppVersionStr = "0.10.20"; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index fe453a2a1..815e3af96 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -37,11 +37,14 @@ Q_DECLARE_METATYPE(Qt::MouseButton); Q_DECLARE_METATYPE(Ui::ShowWay); namespace App { +namespace internal { void CallDelayed(int duration, base::lambda &&lambda) { QTimer::singleShot(duration, base::lambda_slot_once(App::app(), std_::move(lambda)), SLOT(action())); } +} // namespace internal + void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo) { if (auto m = main()) { m->sendBotCommand(peer, bot, cmd, replyTo); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 79a0dcce1..d1227e682 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -32,9 +32,31 @@ class ItemBase; } // namespace InlineBots namespace App { +namespace internal { void CallDelayed(int duration, base::lambda &&lambda); +} // namespace internal + +template +inline void CallDelayed(int duration, base::internal::lambda_guard &&guarded) { + return internal::CallDelayed(duration, [guarded = std_::move(guarded)] { guarded(); }); +} + +template +inline void CallDelayed(int duration, Pointer &&qobject, PointersAndLambda&&... qobjectsAndLambda) { + auto guarded = base::lambda_guarded(std_::forward(qobject), std_::forward(qobjectsAndLambda)...); + return CallDelayed(duration, std_::move(guarded)); +} + +template +inline base::lambda LambdaDelayed(int duration, PointersAndLambda&&... qobjectsAndLambda) { + auto guarded = base::lambda_guarded(std_::forward(qobjectsAndLambda)...); + return [guarded = std_::move(guarded), duration] { + CallDelayed(duration, guarded.clone()); + }; +} + void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo = 0); bool insertBotCommand(const QString &cmd, bool specialGif = false); void activateBotCommand(const HistoryItem *msg, int row, int col); diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index fc2c7b766..92e8b29dd 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -80,6 +80,25 @@ membersInnerDropdown: InnerDropdown(defaultInnerDropdown) { scrollMargin: margins(0px, 5px, 0px, 5px); scrollPadding: margins(0px, 3px, 8px, 3px); } +membersInnerItem: ProfilePeerListItem { + button: OutlineButton { + outlineWidth: 0px; + + textBg: windowBg; + textBgOver: windowBgOver; + + textFg: windowSubTextFg; + textFgOver: windowSubTextFgOver; + + font: normalFont; + padding: margins(11px, 5px, 11px, 5px); + + ripple: defaultRippleAnimation; + } + statusFg: windowSubTextFg; + statusFgOver: windowSubTextFgOver; + statusFgActive: windowActiveTextFg; +} historyFileOutImage: icon {{ "history_file_image", msgOutBg }}; historyFileOutImageSelected: icon {{ "history_file_image", msgOutBgSelected }}; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 3efcb1304..16bffe1de 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_dialogs.h" #include "styles/style_window.h" #include "styles/style_boxes.h" +#include "styles/style_profile.h" #include "boxes/confirmbox.h" #include "boxes/send_files_box.h" #include "boxes/sharebox.h" @@ -129,6 +130,11 @@ HistoryInner::HistoryInner(HistoryWidget *historyWidget, Ui::ScrollArea *scroll, _touchSelectTimer.setSingleShot(true); connect(&_touchSelectTimer, SIGNAL(timeout()), this, SLOT(onTouchSelect())); + auto tmp = App::LambdaDelayed(200, this, [this] { + int a = 0; + }); + tmp(); + setAttribute(Qt::WA_AcceptTouchEvents); connect(&_touchScrollTimer, SIGNAL(timeout()), this, SLOT(onTouchScrollTimer())); @@ -1233,19 +1239,24 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } } if (lnkPhoto) { - _menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextImage()))->setEnabled(true); + _menu->addAction(lang(lng_context_save_image), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, photo = lnkPhoto->photo()] { + savePhotoToFile(photo); + }))->setEnabled(true); _menu->addAction(lang(lng_context_copy_image), this, SLOT(copyContextImage()))->setEnabled(true); } else { - if (lnkDocument && lnkDocument->document()->loading()) { + auto document = lnkDocument->document(); + if (document->loading()) { _menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true); } else { - if (lnkDocument && lnkDocument->document()->loaded() && lnkDocument->document()->isGifv()) { + if (document->loaded() && document->isGifv()) { _menu->addAction(lang(lng_context_save_gif), this, SLOT(saveContextGif()))->setEnabled(true); } - if (lnkDocument && !lnkDocument->document()->filepath(DocumentData::FilePathResolveChecked).isEmpty()) { + if (!document->filepath(DocumentData::FilePathResolveChecked).isEmpty()) { _menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true); } - _menu->addAction(lang(lnkIsVideo ? lng_context_save_video : (lnkIsAudio ? lng_context_save_audio : (lnkIsSong ? lng_context_save_audio_file : lng_context_save_file))), this, SLOT(saveContextFile()))->setEnabled(true); + _menu->addAction(lang(lnkIsVideo ? lng_context_save_video : (lnkIsAudio ? lng_context_save_audio : (lnkIsSong ? lng_context_save_audio_file : lng_context_save_file))), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, document] { + saveDocumentToFile(document); + }))->setEnabled(true); } } if (item && item->hasDirectLink() && isUponSelected != 2 && isUponSelected != -2) { @@ -1304,32 +1315,35 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } } if (item && !isUponSelected) { - bool mediaHasTextForCopy = false; - if (HistoryMedia *media = (msg ? msg->getMedia() : nullptr)) { + auto mediaHasTextForCopy = false; + if (auto media = (msg ? msg->getMedia() : nullptr)) { mediaHasTextForCopy = media->hasTextForCopy(); if (media->type() == MediaTypeWebPage && static_cast(media)->attach()) { media = static_cast(media)->attach(); } if (media->type() == MediaTypeSticker) { - DocumentData *doc = media->getDocument(); - if (doc && doc->sticker() && doc->sticker()->set.type() != mtpc_inputStickerSetEmpty) { - _menu->addAction(lang(doc->sticker()->setInstalled() ? lng_context_pack_info : lng_context_pack_add), _widget, SLOT(onStickerPackInfo())); + if (auto document = media->getDocument()) { + if (document->sticker() && document->sticker()->set.type() != mtpc_inputStickerSetEmpty) { + _menu->addAction(lang(document->sticker()->setInstalled() ? lng_context_pack_info : lng_context_pack_add), _widget, SLOT(onStickerPackInfo())); + } + _menu->addAction(lang(lng_context_save_image), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, document] { + saveDocumentToFile(document); + }))->setEnabled(true); } - - _menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextFile()))->setEnabled(true); } else if (media->type() == MediaTypeGif && !_contextMenuLnk) { - DocumentData *doc = media->getDocument(); - if (doc) { - if (doc->loading()) { + if (auto document = media->getDocument()) { + if (document->loading()) { _menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true); } else { - if (doc->isGifv()) { + if (document->isGifv()) { _menu->addAction(lang(lng_context_save_gif), this, SLOT(saveContextGif()))->setEnabled(true); } - if (!doc->filepath(DocumentData::FilePathResolveChecked).isEmpty()) { + if (!document->filepath(DocumentData::FilePathResolveChecked).isEmpty()) { _menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true); } - _menu->addAction(lang(lng_context_save_file), this, SLOT(saveContextFile()))->setEnabled(true); + _menu->addAction(lang(lng_context_save_file), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, document] { + saveDocumentToFile(document); + }))->setEnabled(true); } } } @@ -1401,11 +1415,7 @@ void HistoryInner::copyContextUrl() { } } -void HistoryInner::saveContextImage() { - PhotoClickHandler *lnk = dynamic_cast(_contextMenuLnk.data()); - if (!lnk) return; - - PhotoData *photo = lnk->photo(); +void HistoryInner::savePhotoToFile(PhotoData *photo) { if (!photo || !photo->date || !photo->loaded()) return; QString file; @@ -1455,30 +1465,22 @@ void HistoryInner::showContextInFolder() { } } -void HistoryInner::saveContextFile() { - if (DocumentClickHandler *lnkDocument = dynamic_cast(_contextMenuLnk.data())) { - DocumentSaveClickHandler::doSave(lnkDocument->document(), true); - } else if (HistoryItem *item = App::contextItem()) { - if (HistoryMedia *media = item->getMedia()) { - if (DocumentData *doc = media->getDocument()) { - DocumentSaveClickHandler::doSave(doc, true); - } - } - } +void HistoryInner::saveDocumentToFile(DocumentData *document) { + DocumentSaveClickHandler::doSave(document, true); } void HistoryInner::saveContextGif() { - if (HistoryItem *item = App::contextItem()) { - if (HistoryMedia *media = item->getMedia()) { - if (DocumentData *doc = media->getDocument()) { - _widget->saveGif(doc); + if (auto item = App::contextItem()) { + if (auto media = item->getMedia()) { + if (auto document = media->getDocument()) { + _widget->saveGif(document); } } } } void HistoryInner::copyContextText() { - HistoryItem *item = App::contextItem(); + auto item = App::contextItem(); if (!item || (item->getMedia() && item->getMedia()->type() == MediaTypeSticker)) { return; } @@ -3089,11 +3091,9 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) connect(audioCapture(), SIGNAL(done(QByteArray,VoiceWaveform,qint32)), this, SLOT(onRecordDone(QByteArray,VoiceWaveform,qint32))); } - _attachToggle->setClickedCallback([this] { - App::CallDelayed(st::historyAttach.ripple.hideDuration, base::lambda_guarded(this, [this] { - chooseAttach(); - })); - }); + _attachToggle->setClickedCallback(App::LambdaDelayed(st::historyAttach.ripple.hideDuration, this, [this] { + chooseAttach(); + })); subscribe(FileDialog::QueryDone(), [this](const FileDialog::QueryUpdate &update) { notifyFileQueryUpdated(update); }); @@ -6235,7 +6235,7 @@ void HistoryWidget::setMembersShowAreaActive(bool active) { void HistoryWidget::onMembersDropdownShow() { if (!_membersDropdown) { _membersDropdown.create(this, st::membersInnerDropdown); - _membersDropdown->setOwnedWidget(new Profile::GroupMembersWidget(_membersDropdown, _peer, Profile::GroupMembersWidget::TitleVisibility::Hidden)); + _membersDropdown->setOwnedWidget(new Profile::GroupMembersWidget(_membersDropdown, _peer, Profile::GroupMembersWidget::TitleVisibility::Hidden, st::membersInnerItem)); _membersDropdown->resizeToWidth(st::membersInnerWidth); _membersDropdown->setMaxHeight(countMembersDropdownHeightMax()); diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index af2fbb6c0..147f501aa 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -139,11 +139,9 @@ public slots: void onParentGeometryChanged(); void copyContextUrl(); - void saveContextImage(); void copyContextImage(); void cancelContextDownload(); void showContextInFolder(); - void saveContextFile(); void saveContextGif(); void copyContextText(); void copySelectedText(); @@ -159,6 +157,8 @@ private slots: private: void itemRemoved(HistoryItem *item); + void savePhotoToFile(PhotoData *photo); + void saveDocumentToFile(DocumentData *document); void touchResetSpeed(); void touchUpdateSpeed(); diff --git a/Telegram/SourceFiles/intro/intro.style b/Telegram/SourceFiles/intro/intro.style index fbe54e65e..f2310e251 100644 --- a/Telegram/SourceFiles/intro/intro.style +++ b/Telegram/SourceFiles/intro/intro.style @@ -51,7 +51,7 @@ introCoverTitle: FlatLabel(defaultFlatLabel) { textFg: introTitleFg; align: align(center); } -introCoverTitleTop: 126px; +introCoverTitleTop: 136px; introCoverDescription: FlatLabel(defaultFlatLabel) { font: font(15px); textFg: introDescriptionFg; @@ -60,12 +60,12 @@ introCoverDescription: FlatLabel(defaultFlatLabel) { introCoverDescriptionTextStyle: TextStyle(defaultTextStyle) { lineHeight: 24px; } -introCoverDescriptionTop: 164px; +introCoverDescriptionTop: 174px; introTitle: FlatLabel(defaultFlatLabel) { font: font(17px semibold); textFg: introTitleFg; } -introTitleTop: 11px; +introTitleTop: 1px; introDescription: FlatLabel(defaultFlatLabel) { font: normalFont; textFg: introDescriptionFg; @@ -73,18 +73,18 @@ introDescription: FlatLabel(defaultFlatLabel) { introDescriptionTextStyle: TextStyle(defaultTextStyle) { lineHeight: 20px; } -introDescriptionTop: 44px; +introDescriptionTop: 34px; introLink: defaultLinkButton; introPlaneWidth: 48px; introPlaneHeight: 38px; -introHeight: 396px; -introStepTopMin: 86px; +introHeight: 406px; +introStepTopMin: 76px; introStepWidth: 380px; -introStepHeight: 256px; +introStepHeight: 266px; introStepHeightAdd: 30px; -introStepHeightFull: 580px; +introStepHeightFull: 590px; introSlideDuration: 200; introCoverDuration: 300; @@ -96,30 +96,28 @@ introNextButton: RoundButton(defaultActiveButton) { font: font(17px semibold); } -introStepFieldTop: 116px; -introPhoneTop: 16px; -introLinkTop: 21px; +introStepFieldTop: 96px; +introPhoneTop: 6px; +introLinkTop: 24px; introCountry: InputField(defaultInputField) { - textMargins: margins(3px, 7px, 3px, 6px); + textMargins: margins(3px, 27px, 3px, 6px); font: font(16px); width: 300px; - height: 41px; + heightMin: 61px; } introCountryCode: InputField(introCountry) { width: 64px; - height: 41px; textAlign: align(top); } introPhone: InputField(introCountry) { - textMargins: margins(12px, 7px, 12px, 6px); + textMargins: margins(12px, 27px, 12px, 6px); width: 225px; - height: 41px; } introCode: introCountry; introName: introCountry; introPassword: introCountry; -introPasswordTop: 94px; -introPasswordHintTop: 146px; +introPasswordTop: 74px; +introPasswordHintTop: 151px; introPasswordHint: FlatLabel(introDescription) { textFg: windowFg; @@ -138,12 +136,12 @@ introResetButton: RoundButton(defaultLightButton) { introResetBottom: 20px; introCountryIcon: icon {{ "intro_country_dropdown", menuIconFg }}; -introCountryIconPosition: point(8px, 17px); +introCountryIconPosition: point(8px, 37px); introSelectDelta: 30px; -introErrorTop: 225px; -introErrorBelowLinkTop: 213px; +introErrorTop: 235px; +introErrorBelowLinkTop: 220px; introErrorDuration: 200; introError: introDescription; diff --git a/Telegram/SourceFiles/intro/introcode.cpp b/Telegram/SourceFiles/intro/introcode.cpp index 7c5fb184b..021a1a5c3 100644 --- a/Telegram/SourceFiles/intro/introcode.cpp +++ b/Telegram/SourceFiles/intro/introcode.cpp @@ -71,7 +71,7 @@ void CodeInput::correctValue(const QString &was, int wasCursor, QString &now, in if (newText != now) { now = newText; setText(now); - updatePlaceholder(); + startPlaceholderAnimation(); } if (newPos != nowCursor) { nowCursor = newPos; @@ -176,7 +176,6 @@ void CodeWidget::finished() { cancelled(); _sentCode.clear(); _code->setText(QString()); - _code->setDisabled(false); } void CodeWidget::cancelled() { @@ -190,18 +189,14 @@ void CodeWidget::stopCheck() { } void CodeWidget::onCheckRequest() { - int32 status = MTP::state(_sentRequest); + auto status = MTP::state(_sentRequest); if (status < 0) { - int32 leftms = -status; + auto leftms = -status; if (leftms >= 1000) { if (_sentRequest) { MTP::cancel(base::take(_sentRequest)); _sentCode.clear(); } - if (!_code->isEnabled()) { - _code->setDisabled(false); - _code->setFocus(); - } } } if (!_sentRequest && status == MTP::RequestSent) { @@ -212,7 +207,6 @@ void CodeWidget::onCheckRequest() { void CodeWidget::codeSubmitDone(const MTPauth_Authorization &result) { stopCheck(); _sentRequest = 0; - _code->setDisabled(false); auto &d = result.c_auth_authorization(); if (d.vuser.type() != mtpc_user || !d.vuser.c_user().is_self()) { // wtf? showCodeError(lang(lng_server_error)); @@ -226,7 +220,6 @@ bool CodeWidget::codeSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { stopCheck(); _sentRequest = 0; - _code->setDisabled(false); showCodeError(lang(lng_flood_error)); return true; } @@ -234,8 +227,7 @@ bool CodeWidget::codeSubmitFail(const RPCError &error) { stopCheck(); _sentRequest = 0; - _code->setDisabled(false); - const QString &err = error.type(); + auto &err = error.type(); if (err == qstr("PHONE_NUMBER_INVALID") || err == qstr("PHONE_CODE_EXPIRED")) { // show error goBack(); return true; @@ -248,7 +240,6 @@ bool CodeWidget::codeSubmitFail(const RPCError &error) { return true; } else if (err == qstr("SESSION_PASSWORD_NEEDED")) { getData()->code = _sentCode; - _code->setDisabled(false); _checkRequest->start(1000); _sentRequest = MTP::send(MTPaccount_GetPassword(), rpcDone(&CodeWidget::gotPassword), rpcFail(&CodeWidget::codeSubmitFail)); return true; @@ -298,7 +289,6 @@ void CodeWidget::callDone(const MTPauth_SentCode &v) { void CodeWidget::gotPassword(const MTPaccount_Password &result) { stopCheck(); _sentRequest = 0; - _code->setDisabled(false); switch (result.type()) { case mtpc_account_noPassword: { // should not happen _code->setFocus(); @@ -317,9 +307,6 @@ void CodeWidget::gotPassword(const MTPaccount_Password &result) { void CodeWidget::submit() { if (_sentRequest) return; - _code->setDisabled(true); - setFocus(); - hideError(); _checkRequest->start(1000); diff --git a/Telegram/SourceFiles/intro/introphone.cpp b/Telegram/SourceFiles/intro/introphone.cpp index 9b98d3cfc..ef1d7beb6 100644 --- a/Telegram/SourceFiles/intro/introphone.cpp +++ b/Telegram/SourceFiles/intro/introphone.cpp @@ -114,20 +114,6 @@ void PhoneWidget::onInputChange() { hidePhoneError(); } -void PhoneWidget::disableAll() { - _phone->setDisabled(true); - _country->setDisabled(true); - _code->setDisabled(true); - setFocus(); -} - -void PhoneWidget::enableAll(bool failed) { - _phone->setDisabled(false); - _country->setDisabled(false); - _code->setDisabled(false); - if (failed) _phone->setFocus(); -} - void PhoneWidget::submit() { if (_sentRequest || isHidden()) return; @@ -137,7 +123,6 @@ void PhoneWidget::submit() { return; } - disableAll(); hidePhoneError(); _checkRequest->start(1000); @@ -151,12 +136,11 @@ void PhoneWidget::stopCheck() { } void PhoneWidget::onCheckRequest() { - int32 status = MTP::state(_sentRequest); + auto status = MTP::state(_sentRequest); if (status < 0) { - int32 leftms = -status; + auto leftms = -status; if (leftms >= 1000) { MTP::cancel(base::take(_sentRequest)); - if (!_phone->isEnabled()) enableAll(true); } } if (!_sentRequest && status == MTP::RequestSent) { @@ -169,7 +153,6 @@ void PhoneWidget::phoneCheckDone(const MTPauth_CheckedPhone &result) { auto &d = result.c_auth_checkedPhone(); if (mtpIsTrue(d.vphone_registered)) { - disableAll(); hidePhoneError(); _checkRequest->start(1000); @@ -178,7 +161,6 @@ void PhoneWidget::phoneCheckDone(const MTPauth_CheckedPhone &result) { _sentRequest = MTP::send(MTPauth_SendCode(MTP_flags(flags), MTP_string(_sentPhone), MTPBool(), MTP_int(ApiId), MTP_string(ApiHash)), rpcDone(&PhoneWidget::phoneSubmitDone), rpcFail(&PhoneWidget::phoneSubmitFail)); } else { showSignup(); - enableAll(true); _sentRequest = 0; } } @@ -186,7 +168,6 @@ void PhoneWidget::phoneCheckDone(const MTPauth_CheckedPhone &result) { void PhoneWidget::phoneSubmitDone(const MTPauth_SentCode &result) { stopCheck(); _sentRequest = 0; - enableAll(true); if (result.type() != mtpc_auth_sentCode) { showPhoneError(lang(lng_server_error)); @@ -209,7 +190,6 @@ void PhoneWidget::phoneSubmitDone(const MTPauth_SentCode &result) { } void PhoneWidget::toSignUp() { - disableAll(); hideError(); // Hide error, but leave the signup label visible. _checkRequest->start(1000); @@ -223,17 +203,15 @@ bool PhoneWidget::phoneSubmitFail(const RPCError &error) { stopCheck(); _sentRequest = 0; showPhoneError(lang(lng_flood_error)); - enableAll(true); return true; } if (MTP::isDefaultHandledError(error)) return false; stopCheck(); _sentRequest = 0; - const QString &err = error.type(); + auto &err = error.type(); if (err == qstr("PHONE_NUMBER_INVALID")) { // show error showPhoneError(lang(lng_bad_phone)); - enableAll(true); return true; } if (cDebug()) { // internal server error @@ -241,7 +219,6 @@ bool PhoneWidget::phoneSubmitFail(const RPCError &error) { } else { showPhoneError(lang(lng_server_error)); } - enableAll(true); return false; } @@ -271,7 +248,6 @@ void PhoneWidget::finished() { rpcClear(); cancelled(); - enableAll(true); } void PhoneWidget::cancelled() { diff --git a/Telegram/SourceFiles/intro/introphone.h b/Telegram/SourceFiles/intro/introphone.h index d7379405e..0dbfedd5b 100644 --- a/Telegram/SourceFiles/intro/introphone.h +++ b/Telegram/SourceFiles/intro/introphone.h @@ -68,8 +68,6 @@ private: void toSignUp(); QString fullNumber() const; - void disableAll(); - void enableAll(bool failed); void stopCheck(); void showPhoneError(const QString &text); diff --git a/Telegram/SourceFiles/intro/intropwdcheck.cpp b/Telegram/SourceFiles/intro/intropwdcheck.cpp index f4b80843c..04a7fa5a9 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.cpp +++ b/Telegram/SourceFiles/intro/intropwdcheck.cpp @@ -102,16 +102,11 @@ void PwdCheckWidget::stopCheck() { } void PwdCheckWidget::onCheckRequest() { - int32 status = MTP::state(_sentRequest); + auto status = MTP::state(_sentRequest); if (status < 0) { - int32 leftms = -status; + auto leftms = -status; if (leftms >= 1000) { MTP::cancel(base::take(_sentRequest)); - if (!_pwdField->isEnabled()) { - _pwdField->setDisabled(false); - _codeField->setDisabled(false); - activate(); - } } } if (!_sentRequest && status == MTP::RequestSent) { @@ -125,8 +120,6 @@ void PwdCheckWidget::pwdSubmitDone(bool recover, const MTPauth_Authorization &re if (recover) { cSetPasswordRecovered(true); } - _pwdField->setDisabled(false); - _codeField->setDisabled(false); auto &d = result.c_auth_authorization(); if (d.vuser.type() != mtpc_user || !d.vuser.c_user().is_self()) { // wtf? showError(lang(lng_server_error)); @@ -139,9 +132,7 @@ bool PwdCheckWidget::pwdSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { _sentRequest = 0; stopCheck(); - _codeField->setDisabled(false); showError(lang(lng_flood_error)); - _pwdField->setDisabled(false); _pwdField->showError(); return true; } @@ -149,9 +140,7 @@ bool PwdCheckWidget::pwdSubmitFail(const RPCError &error) { _sentRequest = 0; stopCheck(); - _pwdField->setDisabled(false); - _codeField->setDisabled(false); - const QString &err = error.type(); + auto &err = error.type(); if (err == qstr("PASSWORD_HASH_INVALID")) { showError(lang(lng_signin_bad_password)); _pwdField->selectAll(); @@ -179,8 +168,6 @@ bool PwdCheckWidget::codeSubmitFail(const RPCError &error) { _sentRequest = 0; stopCheck(); - _pwdField->setDisabled(false); - _codeField->setDisabled(false); const QString &err = error.type(); if (err == qstr("PASSWORD_EMPTY")) { goBack(); @@ -214,8 +201,6 @@ void PwdCheckWidget::recoverStarted(const MTPauth_PasswordRecovery &result) { bool PwdCheckWidget::recoverStartFail(const RPCError &error) { stopCheck(); - _pwdField->setDisabled(false); - _codeField->setDisabled(false); _pwdField->show(); _pwdHint->show(); _codeField->hide(); @@ -283,7 +268,6 @@ void PwdCheckWidget::onInputChange() { void PwdCheckWidget::submit() { if (_sentRequest) return; if (_pwdField->isHidden()) { - if (!_codeField->isEnabled()) return; auto code = _codeField->getLastText().trimmed(); if (code.isEmpty()) { _codeField->showError(); @@ -292,11 +276,6 @@ void PwdCheckWidget::submit() { _sentRequest = MTP::send(MTPauth_RecoverPassword(MTP_string(code)), rpcDone(&PwdCheckWidget::pwdSubmitDone, true), rpcFail(&PwdCheckWidget::codeSubmitFail)); } else { - if (!_pwdField->isEnabled()) return; - - _pwdField->setDisabled(true); - setFocus(); - hideError(); QByteArray pwdData = _salt + _pwdField->getLastText().toUtf8() + _salt, pwdHash(32, Qt::Uninitialized); diff --git a/Telegram/SourceFiles/intro/introsignup.cpp b/Telegram/SourceFiles/intro/introsignup.cpp index cb04b5286..f2c06815a 100644 --- a/Telegram/SourceFiles/intro/introsignup.cpp +++ b/Telegram/SourceFiles/intro/introsignup.cpp @@ -42,13 +42,11 @@ SignupWidget::SignupWidget(QWidget *parent, Widget::Data *data) : Step(parent, d , _checkRequest(this) { connect(_checkRequest, SIGNAL(timeout()), this, SLOT(onCheckRequest())); - _photo->setClickedCallback([this] { - App::CallDelayed(st::defaultActiveButton.ripple.hideDuration, base::lambda_guarded(this, [this] { - auto imgExtensions = cImgExtensions(); - auto filter = qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + filedialogAllFilesFilter(); - _readPhotoFileQueryId = FileDialog::queryReadFile(lang(lng_choose_image), filter); - })); - }); + _photo->setClickedCallback(App::LambdaDelayed(st::defaultActiveButton.ripple.hideDuration, this, [this] { + auto imgExtensions = cImgExtensions(); + auto filter = qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + filedialogAllFilesFilter(); + _readPhotoFileQueryId = FileDialog::queryReadFile(lang(lng_choose_image), filter); + })); subscribe(FileDialog::QueryDone(), [this](const FileDialog::QueryUpdate &update) { notifyFileQueryUpdated(update); }); @@ -96,7 +94,7 @@ void SignupWidget::resizeEvent(QResizeEvent *e) { _photo->moveToLeft(photoRight - _photo->width(), photoTop); auto firstTop = contentTop() + st::introStepFieldTop; - auto secondTop = firstTop + st::introName.height + st::introPhoneTop; + auto secondTop = firstTop + st::introName.heightMin + st::introPhoneTop; if (_invertOrder) { _last->moveToLeft(contentLeft(), firstTop); _first->moveToLeft(contentLeft(), secondTop); @@ -131,20 +129,11 @@ void SignupWidget::stopCheck() { } void SignupWidget::onCheckRequest() { - int32 status = MTP::state(_sentRequest); + auto status = MTP::state(_sentRequest); if (status < 0) { - int32 leftms = -status; + auto leftms = -status; if (leftms >= 1000) { MTP::cancel(base::take(_sentRequest)); - if (!_first->isEnabled()) { - _first->setDisabled(false); - _last->setDisabled(false); - if (_invertOrder) { - _first->setFocus(); - } else { - _last->setFocus(); - } - } } } if (!_sentRequest && status == MTP::RequestSent) { @@ -159,9 +148,7 @@ void SignupWidget::onPhotoReady(const QImage &img) { void SignupWidget::nameSubmitDone(const MTPauth_Authorization &result) { stopCheck(); - _first->setDisabled(false); - _last->setDisabled(false); - const auto &d(result.c_auth_authorization()); + auto &d = result.c_auth_authorization(); if (d.vuser.type() != mtpc_user || !d.vuser.c_user().is_self()) { // wtf? showError(lang(lng_server_error)); return; @@ -172,8 +159,6 @@ void SignupWidget::nameSubmitDone(const MTPauth_Authorization &result) { bool SignupWidget::nameSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { stopCheck(); - _first->setDisabled(false); - _last->setDisabled(false); showError(lang(lng_flood_error)); if (_invertOrder) { _first->setFocus(); @@ -185,9 +170,7 @@ bool SignupWidget::nameSubmitFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; stopCheck(); - _first->setDisabled(false); - _last->setDisabled(false); - const QString &err = error.type(); + auto &err = error.type(); if (err == qstr("PHONE_NUMBER_INVALID") || err == qstr("PHONE_CODE_EXPIRED") || err == qstr("PHONE_CODE_EMPTY") || err == qstr("PHONE_CODE_INVALID") || err == qstr("PHONE_NUMBER_OCCUPIED")) { @@ -220,6 +203,7 @@ void SignupWidget::onInputChange() { } void SignupWidget::submit() { + if (_sentRequest) return; if (_invertOrder) { if ((_last->hasFocus() || _last->getLastText().trimmed().length()) && !_first->getLastText().trimmed().length()) { _first->setFocus(); @@ -237,11 +221,6 @@ void SignupWidget::submit() { return; } } - if (!_first->isEnabled()) return; - - _first->setDisabled(true); - _last->setDisabled(true); - setFocus(); showError(QString()); diff --git a/Telegram/SourceFiles/layerwidget.cpp b/Telegram/SourceFiles/layerwidget.cpp index d9cc061c7..d77c2a9c5 100644 --- a/Telegram/SourceFiles/layerwidget.cpp +++ b/Telegram/SourceFiles/layerwidget.cpp @@ -311,6 +311,7 @@ void LayerStackWidget::setCacheImages() { setFocus(); } if (_mainMenu) { + setAttribute(Qt::WA_OpaquePaintEvent, false); hideChildren(); bodyCache = myGrab(App::wnd()->bodyWidget()); showChildren(); diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 29f328b1c..8ab17cc5f 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -50,27 +50,23 @@ TextParseOptions _documentNameOptions = { } // namespace -void ItemBase::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { +void ItemBase::clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) { App::hoveredLinkItem(active ? _parent : nullptr); Ui::repaintHistoryItem(_parent); } -void ItemBase::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { +void ItemBase::clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) { App::pressedLinkItem(pressed ? _parent : nullptr); Ui::repaintHistoryItem(_parent); } -void RadialProgressItem::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { - if (p == _openl || p == _savel || p == _cancell) { +void RadialProgressItem::clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) { + ItemBase::clickHandlerActiveChanged(action, active); + if (action == _openl || action == _savel || action == _cancell) { if (iconAnimated()) { _a_iconOver.start([this] { Ui::repaintHistoryItem(_parent); }, active ? 0. : 1., active ? 1. : 0., st::msgFileOverDuration); } } - ItemBase::clickHandlerActiveChanged(p, active); -} - -void RadialProgressItem::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { - ItemBase::clickHandlerPressedChanged(p, pressed); } void RadialProgressItem::setLinks(ClickHandlerPtr &&openl, ClickHandlerPtr &&savel, ClickHandlerPtr &&cancell) { @@ -274,12 +270,14 @@ void Photo::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, i } void Photo::clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) { + ItemBase::clickHandlerActiveChanged(action, active); if (_check) { _check->setActive(active); } } void Photo::clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) { + ItemBase::clickHandlerPressedChanged(action, pressed); if (_check) { _check->setPressed(pressed); } diff --git a/Telegram/SourceFiles/overview/overview_layout.h b/Telegram/SourceFiles/overview/overview_layout.h index c5bc3de97..30437a712 100644 --- a/Telegram/SourceFiles/overview/overview_layout.h +++ b/Telegram/SourceFiles/overview/overview_layout.h @@ -76,8 +76,8 @@ public: return _parent; } - void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; - void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool active) override; + void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) override; + void clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) override; protected: HistoryItem *_parent; @@ -90,8 +90,7 @@ public: } RadialProgressItem(const RadialProgressItem &other) = delete; - void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; - void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool active) override; + void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) override; ~RadialProgressItem(); diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index bb837c631..095766417 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -1195,13 +1195,17 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } if (lnkPhoto) { } else { - if (lnkDocument && lnkDocument->document()->loading()) { - _menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true); - } else { - if (lnkDocument && !lnkDocument->document()->filepath(DocumentData::FilePathResolveChecked).isEmpty()) { - _menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true); + if (auto document = lnkDocument->document()) { + if (document->loading()) { + _menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true); + } else { + if (document->filepath(DocumentData::FilePathResolveChecked).isEmpty()) { + _menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true); + } + _menu->addAction(lang(lnkIsVideo ? lng_context_save_video : (lnkIsAudio ? lng_context_save_audio : (lnkIsSong ? lng_context_save_audio_file : lng_context_save_file))), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, document] { + saveDocumentToFile(document); + }))->setEnabled(true); } - _menu->addAction(lang(lnkIsVideo ? lng_context_save_video : (lnkIsAudio ? lng_context_save_audio : (lnkIsSong ? lng_context_save_audio_file : lng_context_save_file))), this, SLOT(saveContextFile()))->setEnabled(true); } } if (isUponSelected > 1) { @@ -1418,30 +1422,27 @@ void OverviewInner::cancelContextDownload() { } void OverviewInner::showContextInFolder() { - if (DocumentClickHandler *lnkDocument = dynamic_cast(_contextMenuLnk.data())) { - QString filepath = lnkDocument->document()->filepath(DocumentData::FilePathResolveChecked); + if (auto lnkDocument = dynamic_cast(_contextMenuLnk.data())) { + auto filepath = lnkDocument->document()->filepath(DocumentData::FilePathResolveChecked); if (!filepath.isEmpty()) { psShowInFolder(filepath); } } } -void OverviewInner::saveContextFile() { - DocumentClickHandler *lnkDocument = dynamic_cast(_contextMenuLnk.data()); - if (lnkDocument) DocumentSaveClickHandler::doSave(lnkDocument->document(), true); +void OverviewInner::saveDocumentToFile(DocumentData *document) { + DocumentSaveClickHandler::doSave(document, true); } bool OverviewInner::onSearchMessages(bool searchCache) { _searchTimer.stop(); - QString q = _search->text().trimmed(); + auto q = _search->text().trimmed(); if (q.isEmpty()) { - if (_searchRequest) { - _searchRequest = 0; - } + MTP::cancel(base::take(_searchRequest)); return true; } if (searchCache) { - SearchCache::const_iterator i = _searchCache.constFind(q); + auto i = _searchCache.constFind(q); if (i != _searchCache.cend()) { _searchQuery = q; _searchFull = _searchFullMigrated = false; diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index 5d6988c6b..851625f09 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -107,7 +107,6 @@ public slots: void copyContextUrl(); void cancelContextDownload(); void showContextInFolder(); - void saveContextFile(); void goToMessage(); void deleteMessage(); @@ -128,6 +127,8 @@ public slots: void onNeedSearchMessages(); private: + void saveDocumentToFile(DocumentData *document); + void itemRemoved(HistoryItem *item); MsgId complexMsgId(const HistoryItem *item) const; diff --git a/Telegram/SourceFiles/passcodewidget.cpp b/Telegram/SourceFiles/passcodewidget.cpp index 06a4166b1..ff9010cd3 100644 --- a/Telegram/SourceFiles/passcodewidget.cpp +++ b/Telegram/SourceFiles/passcodewidget.cpp @@ -32,7 +32,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "window/window_slide_animation.h" PasscodeWidget::PasscodeWidget(QWidget *parent) : TWidget(parent) -, _passcode(this, st::passcodeInput) +, _passcode(this, st::passcodeInput, lang(lng_passcode_ph)) , _submit(this, lang(lng_passcode_submit), st::passcodeSubmit) , _logout(this, lang(lng_passcode_logout)) { connect(_passcode, SIGNAL(changed()), this, SLOT(onChanged())); @@ -42,7 +42,6 @@ PasscodeWidget::PasscodeWidget(QWidget *parent) : TWidget(parent) _logout->setClickedCallback([] { App::wnd()->onLogout(); }); show(); - _passcode->setFocus(); } void PasscodeWidget::onSubmit() { @@ -110,6 +109,8 @@ void PasscodeWidget::showAnimated(const QPixmap &bgAnimCache, bool back) { _a_show.finish(); showAll(); + setFocus(); + _passcode->finishAnimations(); (_showBack ? _cacheUnder : _cacheOver) = myGrab(this); hideAll(); diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style index 946e7025c..c2cfa9709 100644 --- a/Telegram/SourceFiles/profile/profile.style +++ b/Telegram/SourceFiles/profile/profile.style @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org using "basic.style"; using "basic_types.style"; +using "ui/widgets/widgets.style"; using "window/window.style"; profileBg: windowBg; @@ -115,6 +116,12 @@ profileInviteLinkText: FlatLabel(profileBlockTextPart) { profileLimitReachedSkip: 6px; +profileMemberItem: ProfilePeerListItem { + button: defaultLeftOutlineButton; + statusFg: windowSubTextFg; + statusFgOver: #7c99b2; + statusFgActive: windowActiveTextFg; +} profileMemberHeight: 58px; profileMemberPaddingLeft: 16px; profileMemberPhotoSize: 46px; @@ -122,9 +129,6 @@ profileMemberPhotoPosition: point(12px, 6px); profileMemberNamePosition: point(68px, 11px); profileMemberNameFg: #222222; profileMemberStatusPosition: point(68px, 31px); -profileMemberStatusFg: windowSubTextFg; -profileMemberStatusFgOver: #7c99b2; -profileMemberStatusFgActive: windowActiveTextFg; profileMemberAdminIcon: icon {{ "profile_admin_star", windowBgActive, point(4px, 3px) }}; profileLimitReachedLabel: FlatLabel(defaultFlatLabel) { width: 180px; @@ -134,9 +138,9 @@ profileLimitReachedStyle: TextStyle(defaultTextStyle) { lineHeight: 19px; } -profileReportReasonOther: InputArea(defaultInputArea) { - textMargins: margins(1px, 6px, 1px, 4px); - heightMax: 115px; +profileReportReasonOther: InputField(defaultInputField) { + textMargins: margins(1px, 26px, 1px, 4px); + heightMax: 135px; } profileVerifiedCheckShift: -3px; diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.cpp b/Telegram/SourceFiles/profile/profile_block_group_members.cpp index 25cd4bf40..7477315aa 100644 --- a/Telegram/SourceFiles/profile/profile_block_group_members.cpp +++ b/Telegram/SourceFiles/profile/profile_block_group_members.cpp @@ -32,10 +32,11 @@ namespace Profile { using UpdateFlag = Notify::PeerUpdate::Flag; -GroupMembersWidget::GroupMembersWidget(QWidget *parent, PeerData *peer, TitleVisibility titleVisibility) +GroupMembersWidget::GroupMembersWidget(QWidget *parent, PeerData *peer, TitleVisibility titleVisibility, const style::ProfilePeerListItem &st) : PeerListWidget(parent , peer , (titleVisibility == TitleVisibility::Visible) ? lang(lng_profile_participants_section) : QString() + , st , lang(lng_profile_kick)) { _updateOnlineTimer.setSingleShot(true); connect(&_updateOnlineTimer, SIGNAL(timeout()), this, SLOT(onUpdateOnlineDisplay())); diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.h b/Telegram/SourceFiles/profile/profile_block_group_members.h index 9044419e4..b70b6c11d 100644 --- a/Telegram/SourceFiles/profile/profile_block_group_members.h +++ b/Telegram/SourceFiles/profile/profile_block_group_members.h @@ -41,7 +41,7 @@ public: Visible, Hidden, }; - GroupMembersWidget(QWidget *parent, PeerData *peer, TitleVisibility titleVisibility = TitleVisibility::Visible); + GroupMembersWidget(QWidget *parent, PeerData *peer, TitleVisibility titleVisibility = TitleVisibility::Visible, const style::ProfilePeerListItem &st = st::profileMemberItem); int onlineCount() const { return _onlineCount; diff --git a/Telegram/SourceFiles/profile/profile_block_peer_list.cpp b/Telegram/SourceFiles/profile/profile_block_peer_list.cpp index 062d4a530..a6173c5df 100644 --- a/Telegram/SourceFiles/profile/profile_block_peer_list.cpp +++ b/Telegram/SourceFiles/profile/profile_block_peer_list.cpp @@ -32,8 +32,9 @@ PeerListWidget::Item::Item(PeerData *peer) : peer(peer) { PeerListWidget::Item::~Item() = default; -PeerListWidget::PeerListWidget(QWidget *parent, PeerData *peer, const QString &title, const QString &removeText) +PeerListWidget::PeerListWidget(QWidget *parent, PeerData *peer, const QString &title, const style::ProfilePeerListItem &st, const QString &removeText) : BlockWidget(parent, peer, title) +, _st(st) , _removeText(removeText) , _removeWidth(st::normalFont->width(_removeText)) { setMouseTracking(true); @@ -88,7 +89,7 @@ void PeerListWidget::paintItem(Painter &p, int x, int y, Item *item, bool select paintOutlinedRect(p, x, y, memberRowWidth, st::profileMemberHeight); } if (auto &ripple = item->ripple) { - ripple->paint(p, x + st::defaultLeftOutlineButton.outlineWidth, y, width(), ms); + ripple->paint(p, x + _st.button.outlineWidth, y, width(), ms); if (ripple->empty()) { ripple.reset(); } @@ -118,18 +119,20 @@ void PeerListWidget::paintItem(Painter &p, int x, int y, Item *item, bool select item->name.drawLeftElided(p, nameLeft, nameTop, nameWidth, width()); if (item->statusHasOnlineColor) { - p.setPen(st::profileMemberStatusFgActive); + p.setPen(_st.statusFgActive); } else { - p.setPen(selected ? st::profileMemberStatusFgOver : st::profileMemberStatusFg); + p.setPen(selected ? _st.statusFgOver : _st.statusFg); } p.setFont(st::normalFont); p.drawTextLeft(x + st::profileMemberStatusPosition.x(), y + st::profileMemberStatusPosition.y(), width(), item->statusText); } void PeerListWidget::paintOutlinedRect(Painter &p, int x, int y, int w, int h) const { - int outlineWidth = st::defaultLeftOutlineButton.outlineWidth; - p.fillRect(rtlrect(x, y, outlineWidth, h, width()), st::defaultLeftOutlineButton.outlineFgOver); - p.fillRect(rtlrect(x + outlineWidth, y, w - outlineWidth, h, width()), st::defaultLeftOutlineButton.textBgOver); + auto outlineWidth = _st.button.outlineWidth; + if (outlineWidth) { + p.fillRect(rtlrect(x, y, outlineWidth, h, width()), _st.button.outlineFgOver); + } + p.fillRect(rtlrect(x + outlineWidth, y, w - outlineWidth, h, width()), _st.button.textBgOver); } void PeerListWidget::mouseMoveEvent(QMouseEvent *e) { @@ -147,12 +150,12 @@ void PeerListWidget::mousePressEvent(QMouseEvent *e) { auto item = _items[_pressed]; if (!item->ripple) { auto memberRowWidth = rowWidth(); - auto mask = Ui::RippleAnimation::rectMask(QSize(memberRowWidth - st::defaultLeftOutlineButton.outlineWidth, st::profileMemberHeight)); - item->ripple = std_::make_unique(st::defaultLeftOutlineButton.ripple, std_::move(mask), [this, index = _pressed] { + auto mask = Ui::RippleAnimation::rectMask(QSize(memberRowWidth - _st.button.outlineWidth, st::profileMemberHeight)); + item->ripple = std_::make_unique(_st.button.ripple, std_::move(mask), [this, index = _pressed] { repaintRow(index); }); } - auto left = getListLeft() + st::defaultLeftOutlineButton.outlineWidth; + auto left = getListLeft() + _st.button.outlineWidth; auto top = getListTop() + st::profileMemberHeight * _pressed; item->ripple->add(e->pos() - QPoint(left, top)); } diff --git a/Telegram/SourceFiles/profile/profile_block_peer_list.h b/Telegram/SourceFiles/profile/profile_block_peer_list.h index c88dbb3c2..090d89c5c 100644 --- a/Telegram/SourceFiles/profile/profile_block_peer_list.h +++ b/Telegram/SourceFiles/profile/profile_block_peer_list.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "profile/profile_block_widget.h" +#include "styles/style_profile.h" namespace Ui { class RippleAnimation; @@ -34,7 +35,7 @@ namespace Profile { class PeerListWidget : public BlockWidget { public: - PeerListWidget(QWidget *parent, PeerData *peer, const QString &title, const QString &removeText = QString()); + PeerListWidget(QWidget *parent, PeerData *peer, const QString &title, const style::ProfilePeerListItem &st = st::profileMemberItem, const QString &removeText = QString()); void setVisibleTopBottom(int visibleTop, int visibleBottom) override; @@ -123,6 +124,8 @@ private: void paintItem(Painter &p, int x, int y, Item *item, bool selected, bool selectedRemove, TimeMs ms); + const style::ProfilePeerListItem &_st; + base::lambda _preloadMoreCallback; base::lambda _selectedCallback; base::lambda _removedCallback; diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index 75e941787..766ea6484 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -486,10 +486,12 @@ void CoverWidget::onShareContact() { } void CoverWidget::onSetPhoto() { - QStringList imgExtensions(cImgExtensions()); - QString filter(qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + filedialogAllFilesFilter()); + App::CallDelayed(st::profilePrimaryButton.ripple.hideDuration, this, [this] { + QStringList imgExtensions(cImgExtensions()); + QString filter(qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + filedialogAllFilesFilter()); - _setPhotoFileQueryId = FileDialog::queryReadFile(lang(lng_choose_image), filter); + _setPhotoFileQueryId = FileDialog::queryReadFile(lang(lng_choose_image), filter); + }); } void CoverWidget::notifyFileQueryUpdated(const FileDialog::QueryUpdate &update) { diff --git a/Telegram/SourceFiles/settings/settings_cover.cpp b/Telegram/SourceFiles/settings/settings_cover.cpp index d923ce09b..3c7b7dcfa 100644 --- a/Telegram/SourceFiles/settings/settings_cover.cpp +++ b/Telegram/SourceFiles/settings/settings_cover.cpp @@ -50,7 +50,7 @@ CoverWidget::CoverWidget(QWidget *parent, UserData *self) : BlockWidget(parent, _name->setSelectable(true); _name->setContextCopyText(lang(lng_profile_copy_fullname)); - connect(_setPhoto, SIGNAL(clicked()), this, SLOT(onSetPhoto())); + _setPhoto->setClickedCallback(App::LambdaDelayed(st::settingsPrimaryButton.ripple.hideDuration, this, [this] { onSetPhoto(); })); connect(_editName, SIGNAL(clicked()), this, SLOT(onEditName())); connect(_editNameInline, SIGNAL(clicked()), this, SLOT(onEditName())); diff --git a/Telegram/SourceFiles/settings/settings_scale_widget.cpp b/Telegram/SourceFiles/settings/settings_scale_widget.cpp index f114ea7a1..c3cca137a 100644 --- a/Telegram/SourceFiles/settings/settings_scale_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_scale_widget.cpp @@ -127,9 +127,7 @@ void ScaleWidget::onRestartNow() { } void ScaleWidget::onCancel() { - App::CallDelayed(st::boxDuration, base::lambda_guarded(this, [this] { - setScale(cRealScale()); - })); + App::CallDelayed(st::boxDuration, this, [this] { setScale(cRealScale()); }); } } // namespace Settings diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 87ebdb7f2..086d84ab7 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -1091,20 +1091,20 @@ void GifOpenClickHandler::onClickImpl() const { void DocumentSaveClickHandler::doSave(DocumentData *data, bool forceSavingAs) { if (!data->date) return; - QString filepath = data->filepath(DocumentData::FilePathResolveSaveFromDataSilent, forceSavingAs); + auto filepath = data->filepath(DocumentData::FilePathResolveSaveFromDataSilent, forceSavingAs); if (!filepath.isEmpty() && !forceSavingAs) { - QPoint pos(QCursor::pos()); + auto pos = QCursor::pos(); if (!psShowOpenWithMenu(pos.x(), pos.y(), filepath)) { psOpenFile(filepath, true); } } else { - QFileInfo fileinfo(filepath); - QDir filedir(filepath.isEmpty() ? QDir() : fileinfo.dir()); - QString filename(filepath.isEmpty() ? QString() : fileinfo.fileName()); - QString newfname = documentSaveFilename(data, forceSavingAs, filename, filedir); + auto fileinfo = QFileInfo(filepath); + auto filedir = filepath.isEmpty() ? QDir() : fileinfo.dir(); + auto filename = filepath.isEmpty() ? QString() : fileinfo.fileName(); + auto newfname = documentSaveFilename(data, forceSavingAs, filename, filedir); if (!newfname.isEmpty()) { - ActionOnLoad action = filename.isEmpty() ? ActionOnLoadNone : ActionOnLoadOpenWith; - FullMsgId actionMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId()); + auto action = (filename.isEmpty() || forceSavingAs) ? ActionOnLoadNone : ActionOnLoadOpenWith; + auto actionMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId()); data->save(newfname, action, actionMsgId); } } diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp index bf8c3cfe1..acab902a7 100644 --- a/Telegram/SourceFiles/ui/countryinput.cpp +++ b/Telegram/SourceFiles/ui/countryinput.cpp @@ -95,7 +95,16 @@ CountryInput::CountryInput(QWidget *parent, const style::InputField &st) : TWidg , _st(st) , _text(lang(lng_country_code)) { initCountries(); - resize(_st.width, _st.height); + resize(_st.width, _st.heightMin); + + auto availableWidth = width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1; + auto placeholderFont = _st.placeholderFont->f; + placeholderFont.setStyleStrategy(QFont::PreferMatch); + auto metrics = QFontMetrics(placeholderFont); + auto placeholder = QString();// metrics.elidedText(lang(lng_country_fake_ph), Qt::ElideRight, availableWidth); + if (!placeholder.isNull()) { + _placeholderPath.addText(0, QFontMetrics(placeholderFont).ascent(), placeholderFont, placeholder); + } } void CountryInput::paintEvent(QPaintEvent *e) { @@ -106,7 +115,7 @@ void CountryInput::paintEvent(QPaintEvent *e) { p.fillRect(r, _st.textBg); } if (_st.border) { - p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg->b); + p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg); } st::introCountryIcon.paint(p, width() - st::introCountryIcon.width() - st::introCountryIconPosition.x(), st::introCountryIconPosition.y(), width()); @@ -114,6 +123,30 @@ void CountryInput::paintEvent(QPaintEvent *e) { p.setFont(_st.font); p.setPen(_st.textFg); p.drawText(rect().marginsRemoved(_st.textMargins), _text, _st.textAlign); + if (!_placeholderPath.isEmpty()) { + auto placeholderShiftDegree = 1.; + p.save(); + p.setClipRect(r); + + auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree); + + QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins)); + r.moveTop(r.top() + placeholderTop); + if (rtl()) r.moveLeft(width() - r.left() - r.width()); + + auto placeholderScale = 1. - (1. - _st.placeholderScale) * placeholderShiftDegree; + auto placeholderFg = anim::color(_st.placeholderFg, _st.placeholderFgActive, 0.); + placeholderFg = anim::color(placeholderFg, _st.placeholderFgError, 0.); + + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(placeholderFg); + p.translate(r.topLeft()); + p.scale(placeholderScale, placeholderScale); + p.drawPath(_placeholderPath); + + p.restore(); + } } void CountryInput::mouseMoveEvent(QMouseEvent *e) { diff --git a/Telegram/SourceFiles/ui/countryinput.h b/Telegram/SourceFiles/ui/countryinput.h index 014b872e8..6eaf18575 100644 --- a/Telegram/SourceFiles/ui/countryinput.h +++ b/Telegram/SourceFiles/ui/countryinput.h @@ -56,6 +56,7 @@ private: const style::InputField &_st; bool _active = false; QString _text; + QPainterPath _placeholderPath; }; diff --git a/Telegram/SourceFiles/ui/effects/widget_fade_wrap.cpp b/Telegram/SourceFiles/ui/effects/widget_fade_wrap.cpp index aa0678c5b..3d3533f70 100644 --- a/Telegram/SourceFiles/ui/effects/widget_fade_wrap.cpp +++ b/Telegram/SourceFiles/ui/effects/widget_fade_wrap.cpp @@ -29,7 +29,8 @@ FadeAnimation::FadeAnimation(TWidget *widget) : _widget(widget) { bool FadeAnimation::paint(Painter &p) { if (_cache.isNull()) return false; - p.setOpacity(_animation.current(getms(), _visible ? 1. : 0.)); + auto opacity = _animation.current(getms(), _visible ? 1. : 0.); + p.setOpacity(opacity); p.drawPixmap(0, 0, _cache); return true; } @@ -64,7 +65,9 @@ void FadeAnimation::stopAnimation() { if (!_cache.isNull()) { _cache = QPixmap(); updateCallback(); - _widget->showChildren(); + if (_visible) { + _widget->showChildren(); + } if (_finishedCallback) { _finishedCallback(); } diff --git a/Telegram/SourceFiles/ui/widgets/input_fields.cpp b/Telegram/SourceFiles/ui/widgets/input_fields.cpp index 45e5cc9ef..65c225117 100644 --- a/Telegram/SourceFiles/ui/widgets/input_fields.cpp +++ b/Telegram/SourceFiles/ui/widgets/input_fields.cpp @@ -1613,28 +1613,11 @@ QSize FlatInput::minimumSizeHint() const { return geometry().size(); } -void FlatInput::setPlaceholder(const QString &ph) { - _fullph = ph; - updatePlaceholderText(); -} - -void FlatInput::setPlaceholderFast(bool fast) { - _fastph = fast; - if (_fastph) { - _a_placeholderVisible.finish(); - update(); - } -} - void FlatInput::updatePlaceholder() { auto placeholderVisible = text().isEmpty(); if (_placeholderVisible != placeholderVisible) { _placeholderVisible = placeholderVisible; - if (_fastph) { - update(); - } else { - _a_placeholderVisible.start([this] { update(); }, _placeholderVisible ? 0. : 1., _placeholderVisible ? 1. : 0., _st.phDuration); - } + _a_placeholderVisible.start([this] { update(); }, _placeholderVisible ? 0. : 1., _placeholderVisible ? 1. : 0., _st.phDuration); } } @@ -1702,72 +1685,55 @@ void FlatInput::onTextChange(const QString &text) { if (App::wnd()) App::wnd()->updateGlobalMenu(); } -InputArea::InputArea(QWidget *parent, const style::InputArea &st, const QString &ph, const QString &val) : TWidget(parent) -, _maxLength(-1) +InputArea::InputArea(QWidget *parent, const style::InputField &st, const QString &ph, const QString &val) : TWidget(parent) +, _st(st) , _inner(this) , _oldtext(val) - -, _ctrlEnterSubmit(CtrlEnterSubmitCtrlEnter) -, _undoAvailable(false) -, _redoAvailable(false) -, _inHeightCheck(false) - -, _customUpDown(false) - -, _placeholderFull(ph) -, _placeholderVisible(val.isEmpty()) - -, _a_border(animation(this, &InputArea::step_border)) - -, _focused(false) -, _error(false) - -, _st(st) - -, _touchPress(false) -, _touchRightButton(false) -, _touchMove(false) -, _correcting(false) { - _inner.setAcceptRichText(false); +, _placeholderFull(ph) { + _inner->setAcceptRichText(false); resize(_st.width, _st.heightMin); setAttribute(Qt::WA_OpaquePaintEvent); - _inner.setFont(_st.font->f); + _inner->setFont(_st.font->f); - _placeholder = _st.font->elided(_placeholderFull, width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1); + createPlaceholderPath(); QPalette p(palette()); p.setColor(QPalette::Text, _st.textFg->c); setPalette(p); - _inner.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - _inner.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _inner->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _inner->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - _inner.setFrameStyle(QFrame::NoFrame | QFrame::Plain); - _inner.viewport()->setAutoFillBackground(false); + _inner->setFrameStyle(QFrame::NoFrame | QFrame::Plain); + _inner->viewport()->setAutoFillBackground(false); - _inner.setContentsMargins(0, 0, 0, 0); - _inner.document()->setDocumentMargin(0); + _inner->setContentsMargins(0, 0, 0, 0); + _inner->document()->setDocumentMargin(0); setAttribute(Qt::WA_AcceptTouchEvents); - _inner.viewport()->setAttribute(Qt::WA_AcceptTouchEvents); + _inner->viewport()->setAttribute(Qt::WA_AcceptTouchEvents); _touchTimer.setSingleShot(true); connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); - connect(_inner.document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(onDocumentContentsChange(int,int,int))); - connect(_inner.document(), SIGNAL(contentsChanged()), this, SLOT(onDocumentContentsChanged())); - connect(&_inner, SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool))); - connect(&_inner, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool))); - if (App::wnd()) connect(&_inner, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu())); + connect(_inner->document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(onDocumentContentsChange(int,int,int))); + connect(_inner->document(), SIGNAL(contentsChanged()), this, SLOT(onDocumentContentsChanged())); + connect(_inner, SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool))); + connect(_inner, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool))); + if (App::wnd()) connect(_inner, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu())); setCursor(style::cur_text); heightAutoupdated(); if (!val.isEmpty()) { - _inner.setPlainText(val); + _inner->setPlainText(val); } - _inner.document()->clearUndoRedoStacks(); + _inner->document()->clearUndoRedoStacks(); + + startBorderAnimation(); + startPlaceholderAnimation(); + finishAnimations(); } void InputArea::onTouchTimer() { @@ -1780,7 +1746,7 @@ bool InputArea::heightAutoupdated() { myEnsureResized(this); - int newh = qCeil(_inner.document()->size().height()) + _st.textMargins.top() + _st.textMargins.bottom(); + int newh = qCeil(_inner->document()->size().height()) + _st.textMargins.top() + _st.textMargins.bottom(); if (newh > _st.heightMax) { newh = _st.heightMax; } else if (newh < _st.heightMin) { @@ -1857,72 +1823,122 @@ void InputArea::touchEvent(QTouchEvent *e) { void InputArea::paintEvent(QPaintEvent *e) { Painter p(this); - QRect r(rect().intersected(e->rect())); + auto ms = getms(); + auto r = rect().intersected(e->rect()); p.fillRect(r, _st.textBg); if (_st.border) { p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg); } - if (_st.borderActive && a_borderOpacityActive.current() > 0) { - auto borderFgActive = anim::color(_st.borderFg, _st.borderFgActive, a_borderFgActive.current()); - auto borderFg = anim::color(borderFgActive, _st.borderFgError, a_borderFgError.current()); - p.setOpacity(a_borderOpacityActive.current()); - p.fillRect(0, height() - _st.borderActive, width(), _st.borderActive, borderFg); - p.setOpacity(1); + auto errorDegree = _a_error.current(ms, _error ? 1. : 0.); + auto focusedDegree = _a_focused.current(ms, hasFocus() ? 1. : 0.); + auto borderShownDegree = _a_borderShown.current(ms, 1.); + auto borderOpacity = _a_borderOpacity.current(ms, _borderVisible ? 1. : 0.); + if (_st.borderActive && (borderOpacity > 0.)) { + auto borderStart = snap(_borderAnimationStart, 0, width()); + auto borderFrom = qRound(borderStart * (1. - borderShownDegree)); + auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree); + if (borderTo > borderFrom) { + auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree); + p.setOpacity(borderOpacity); + p.fillRect(borderFrom, height() - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg); + p.setOpacity(1); + } } - auto ms = getms(); - auto placeholderOpacity = _a_placeholderVisible.current(ms, _placeholderVisible ? 1. : 0.); - if (placeholderOpacity > 0.) { - p.setOpacity(placeholderOpacity); + if (_st.placeholderScale > 0. && !_placeholderPath.isEmpty()) { + auto placeholderShiftDegree = _a_placeholderShifted.current(ms, _placeholderShifted ? 1. : 0.); p.save(); p.setClipRect(r); - auto placeholderLeft = anim::interpolate(_st.placeholderShift, 0, placeholderOpacity); + auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree); QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins)); - r.moveLeft(r.left() + placeholderLeft); + r.moveTop(r.top() + placeholderTop); if (rtl()) r.moveLeft(width() - r.left() - r.width()); - p.setFont(_st.font); - p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, _a_placeholderFocused.current(ms, hasFocus() ? 1. : 0.))); - p.drawText(r, _placeholder, _st.placeholderAlign); + auto placeholderScale = 1. - (1. - _st.placeholderScale) * placeholderShiftDegree; + auto placeholderFg = anim::color(_st.placeholderFg, _st.placeholderFgActive, focusedDegree); + placeholderFg = anim::color(placeholderFg, _st.placeholderFgError, errorDegree); + + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(placeholderFg); + p.translate(r.topLeft()); + p.scale(placeholderScale, placeholderScale); + p.drawPath(_placeholderPath); p.restore(); + } else if (!_placeholder.isEmpty()) { + auto placeholderHiddenDegree = _a_placeholderShifted.current(ms, _placeholderShifted ? 1. : 0.); + if (placeholderHiddenDegree < 1.) { + p.setOpacity(1. - placeholderHiddenDegree); + p.save(); + p.setClipRect(r); + + auto placeholderLeft = anim::interpolate(0, -_st.placeholderShift, placeholderHiddenDegree); + + auto r = rect().marginsRemoved(_st.textMargins + _st.placeholderMargins); + r.moveLeft(r.left() + placeholderLeft); + if (rtl()) r.moveLeft(width() - r.left() - r.width()); + + p.setFont(_st.font); + p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree)); + p.drawText(r, _placeholder, _st.placeholderAlign); + + p.restore(); + } } TWidget::paintEvent(e); } void InputArea::startBorderAnimation() { - a_borderFgActive.start(_focused ? 1. : 0.); - a_borderFgError.start(_error ? 1. : 0.); - a_borderOpacityActive.start((_error || _focused) ? 1 : 0); - _a_border.start(); + auto borderVisible = (_error || _focused); + if (_borderVisible != borderVisible) { + _borderVisible = borderVisible; + if (_borderVisible) { + if (_a_borderOpacity.animating()) { + _a_borderOpacity.start([this] { update(); }, 0., 1., _st.duration); + } else { + _a_borderShown.start([this] { update(); }, 0., 1., _st.duration); + } + } else { + _a_borderOpacity.start([this] { update(); }, 1., 0., _st.duration); + } + } } void InputArea::focusInEvent(QFocusEvent *e) { - QTimer::singleShot(0, &_inner, SLOT(setFocus())); + _borderAnimationStart = (e->reason() == Qt::MouseFocusReason) ? mapFromGlobal(QCursor::pos()).x() : (width() / 2); + QTimer::singleShot(0, this, SLOT(onFocusInner())); } void InputArea::mousePressEvent(QMouseEvent *e) { - QTimer::singleShot(0, &_inner, SLOT(setFocus())); + _borderAnimationStart = e->pos().x(); + QTimer::singleShot(0, this, SLOT(onFocusInner())); +} + +void InputArea::onFocusInner() { + auto borderStart = _borderAnimationStart; + _inner->setFocus(); + _borderAnimationStart = borderStart; } void InputArea::contextMenuEvent(QContextMenuEvent *e) { - _inner.contextMenuEvent(e); + _inner->contextMenuEvent(e); } void InputArea::Inner::focusInEvent(QFocusEvent *e) { - f()->focusInInner(); + f()->focusInInner(e->reason() == Qt::MouseFocusReason); QTextEdit::focusInEvent(e); emit f()->focused(); } -void InputArea::focusInInner() { +void InputArea::focusInInner(bool focusByMouse) { if (!_focused) { _focused = true; - - _a_placeholderFocused.start([this] { update(); }, 0., 1., _st.duration); - + _borderAnimationStart = focusByMouse ? mapFromGlobal(QCursor::pos()).x() : (width() / 2); + _a_focused.start([this] { update(); }, 0., 1., _st.duration); + startPlaceholderAnimation(); startBorderAnimation(); } } @@ -1936,9 +1952,8 @@ void InputArea::Inner::focusOutEvent(QFocusEvent *e) { void InputArea::focusOutInner() { if (_focused) { _focused = false; - - _a_placeholderFocused.start([this] { update(); }, 1., 0., _st.duration); - + _a_focused.start([this] { update(); }, 1., 0., _st.duration); + startPlaceholderAnimation(); startBorderAnimation(); } } @@ -1957,7 +1972,7 @@ QString InputArea::getText(int32 start, int32 end) const { if (start < 0) start = 0; bool full = (start == 0) && (end < 0); - QTextDocument *doc(_inner.document()); + QTextDocument *doc(_inner->document()); QTextBlock from = full ? doc->begin() : doc->findBlock(start), till = (end < 0) ? doc->end() : doc->findBlock(end); if (till.isValid()) till = till.next(); @@ -2029,7 +2044,7 @@ QString InputArea::getText(int32 start, int32 end) const { } bool InputArea::hasText() const { - QTextDocument *doc(_inner.document()); + QTextDocument *doc(_inner->document()); QTextBlock from = doc->begin(), till = doc->end(); if (from == till) return false; @@ -2077,11 +2092,11 @@ void InputArea::processDocumentContentsChange(int position, int charsAdded) { const EmojiData *emoji = 0; static QString regular = qsl("Open Sans"), semibold = qsl("Open Sans Semibold"); - bool checkTilde = !cRetina() && (_inner.font().pixelSize() == 13) && (_inner.font().family() == regular); + bool checkTilde = !cRetina() && (_inner->font().pixelSize() == 13) && (_inner->font().family() == regular); bool wasTildeFragment = false; - QTextDocument *doc(_inner.document()); - QTextCursor c(_inner.textCursor()); + QTextDocument *doc(_inner->document()); + QTextCursor c(_inner->textCursor()); c.joinPreviousEditBlock(); while (true) { int32 start = position, end = position + charsAdded; @@ -2141,8 +2156,8 @@ void InputArea::processDocumentContentsChange(int position, int charsAdded) { if (replacePosition >= 0) break; } if (replacePosition >= 0) { - if (!_inner.document()->pageSize().isNull()) { - _inner.document()->setPageSize(QSizeF(0, 0)); + if (!_inner->document()->pageSize().isNull()) { + _inner->document()->setPageSize(QSizeF(0, 0)); } QTextCursor c(doc->docHandle(), 0); c.setPosition(replacePosition); @@ -2170,10 +2185,10 @@ void InputArea::onDocumentContentsChange(int position, int charsRemoved, int cha if (_correcting) return; QString oldtext(_oldtext); - QTextCursor(_inner.document()->docHandle(), 0).joinPreviousEditBlock(); + QTextCursor(_inner->document()->docHandle(), 0).joinPreviousEditBlock(); if (!position) { // Qt bug workaround https://bugreports.qt.io/browse/QTBUG-49062 - QTextCursor c(_inner.document()->docHandle(), 0); + QTextCursor c(_inner->document()->docHandle(), 0); c.movePosition(QTextCursor::End); if (position + charsAdded > c.position()) { int32 toSubstract = position + charsAdded - c.position(); @@ -2186,7 +2201,7 @@ void InputArea::onDocumentContentsChange(int position, int charsRemoved, int cha _correcting = true; if (_maxLength >= 0) { - QTextCursor c(_inner.document()->docHandle(), 0); + QTextCursor c(_inner->document()->docHandle(), 0); c.movePosition(QTextCursor::End); int32 fullSize = c.position(), toRemove = fullSize - _maxLength; if (toRemove > 0) { @@ -2212,9 +2227,9 @@ void InputArea::onDocumentContentsChange(int position, int charsRemoved, int cha } _correcting = false; - QTextCursor(_inner.document()->docHandle(), 0).endEditBlock(); + QTextCursor(_inner->document()->docHandle(), 0).endEditBlock(); - if (_inner.document()->availableRedoSteps() > 0) return; + if (_inner->document()->availableRedoSteps() > 0) return; const int takeBack = 3; @@ -2227,10 +2242,10 @@ void InputArea::onDocumentContentsChange(int position, int charsRemoved, int cha if (charsAdded <= 0) return; _correcting = true; - QSizeF s = _inner.document()->pageSize(); + QSizeF s = _inner->document()->pageSize(); processDocumentContentsChange(position, charsAdded); - if (_inner.document()->pageSize() != s) { - _inner.document()->setPageSize(s); + if (_inner->document()->pageSize() != s) { + _inner->document()->setPageSize(s); } _correcting = false; } @@ -2238,18 +2253,15 @@ void InputArea::onDocumentContentsChange(int position, int charsRemoved, int cha void InputArea::onDocumentContentsChanged() { if (_correcting) return; - if (_error) { - _error = false; - startBorderAnimation(); - } + setErrorShown(false); - QString curText(getText()); + auto curText = getText(); if (_oldtext != curText) { _oldtext = curText; emit changed(); checkContentHeight(); } - updatePlaceholder(); + startPlaceholderAnimation(); if (App::wnd()) App::wnd()->updateGlobalMenu(); } @@ -2263,27 +2275,20 @@ void InputArea::onRedoAvailable(bool avail) { if (App::wnd()) App::wnd()->updateGlobalMenu(); } -void InputArea::step_border(float64 ms, bool timer) { - float64 dt = ms / _st.duration; - bool res = true; - if (dt >= 1) { - _a_border.stop(); - a_borderFgActive.finish(); - a_borderFgError.finish(); - a_borderOpacityActive.finish(); - } else { - a_borderFgActive.update(dt, anim::linear); - a_borderFgError.update(dt, anim::linear); - a_borderOpacityActive.update(dt, anim::linear); - } - if (timer) update(); +void InputArea::finishAnimations() { + _a_focused.finish(); + _a_error.finish(); + _a_placeholderShifted.finish(); + _a_borderShown.finish(); + _a_borderOpacity.finish(); + update(); } -void InputArea::updatePlaceholder() { - auto placeholderVisible = _oldtext.isEmpty(); - if (_placeholderVisible != placeholderVisible) { - _placeholderVisible = placeholderVisible; - _a_placeholderVisible.start([this] { update(); }, _placeholderVisible ? 0. : 1., _placeholderVisible ? 1. : 0., _st.duration); +void InputArea::startPlaceholderAnimation() { + auto placeholderShifted = (_focused && _st.placeholderScale > 0.) || !getLastText().isEmpty(); + if (_placeholderShifted != placeholderShifted) { + _placeholderShifted = placeholderShifted; + _a_placeholderShifted.start([this] { update(); }, _placeholderShifted ? 0. : 1., _placeholderShifted ? 1. : 0., _st.duration); } } @@ -2310,8 +2315,8 @@ void InputArea::Inner::keyPressEvent(QKeyEvent *e) { bool macmeta = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier); bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier); bool ctrlGood = (ctrl && shift) || - (ctrl && (f()->_ctrlEnterSubmit == CtrlEnterSubmitCtrlEnter || f()->_ctrlEnterSubmit == CtrlEnterSubmitBoth)) || - (!ctrl && !shift && (f()->_ctrlEnterSubmit == CtrlEnterSubmitEnter || f()->_ctrlEnterSubmit == CtrlEnterSubmitBoth)); + (ctrl && (f()->_ctrlEnterSubmit == CtrlEnterSubmit::CtrlEnter || f()->_ctrlEnterSubmit == CtrlEnterSubmit::Both)) || + (!ctrl && !shift && (f()->_ctrlEnterSubmit == CtrlEnterSubmit::Enter || f()->_ctrlEnterSubmit == CtrlEnterSubmit::Both)); bool enter = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return); if (macmeta && e->key() == Qt::Key_Backspace) { @@ -2370,10 +2375,6 @@ void InputArea::Inner::keyPressEvent(QKeyEvent *e) { } } -void InputArea::Inner::paintEvent(QPaintEvent *e) { - return QTextEdit::paintEvent(e); -} - void InputArea::Inner::contextMenuEvent(QContextMenuEvent *e) { if (auto menu = createStandardContextMenu()) { (new Ui::PopupMenu(menu))->popup(e->globalPos()); @@ -2381,88 +2382,96 @@ void InputArea::Inner::contextMenuEvent(QContextMenuEvent *e) { } void InputArea::resizeEvent(QResizeEvent *e) { - _placeholder = _st.font->elided(_placeholderFull, width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1); - _inner.setGeometry(rect().marginsRemoved(_st.textMargins)); + createPlaceholderPath(); + _inner->setGeometry(rect().marginsRemoved(_st.textMargins)); + _borderAnimationStart = width() / 2; TWidget::resizeEvent(e); checkContentHeight(); } -void InputArea::showError() { - _error = true; - if (hasFocus()) { - startBorderAnimation(); +void InputArea::createPlaceholderPath() { + auto availableWidth = width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1; + if (_st.placeholderScale > 0.) { + auto placeholderFont = _st.placeholderFont->f; + placeholderFont.setStyleStrategy(QFont::PreferMatch); + auto metrics = QFontMetrics(placeholderFont); + _placeholder = metrics.elidedText(_placeholderFull, Qt::ElideRight, availableWidth); + _placeholderPath = QPainterPath(); + if (!_placeholder.isEmpty()) { + _placeholderPath.addText(0, QFontMetrics(placeholderFont).ascent(), placeholderFont, _placeholder); + } } else { - _inner.setFocus(); + _placeholder = _st.placeholderFont->elided(_placeholderFull, availableWidth); + } +} + +void InputArea::showError() { + setErrorShown(true); + if (!hasFocus()) { + _inner->setFocus(); + } +} + +void InputArea::setErrorShown(bool error) { + if (_error != error) { + _error = error; + _a_error.start([this] { update(); }, _error ? 0. : 1., _error ? 1. : 0., _st.duration); + startBorderAnimation(); } } InputField::InputField(QWidget *parent, const style::InputField &st, const QString &ph, const QString &val) : TWidget(parent) -, _maxLength(-1) +, _st(st) , _inner(this) , _oldtext(val) +, _placeholderFull(ph) { + _inner->setAcceptRichText(false); + resize(_st.width, _st.heightMin); -, _undoAvailable(false) -, _redoAvailable(false) - -, _customUpDown(true) - -, _placeholderFull(ph) -, _placeholderVisible(val.isEmpty()) - -, _a_border(animation(this, &InputField::step_border)) - -, _focused(false) -, _error(false) - -, _st(st) - -, _touchPress(false) -, _touchRightButton(false) -, _touchMove(false) -, _correcting(false) { - _inner.setAcceptRichText(false); - resize(_st.width, _st.height); - - _inner.setWordWrapMode(QTextOption::NoWrap); + _inner->setWordWrapMode(QTextOption::NoWrap); if (_st.textBg->c.alphaF() >= 1.) { setAttribute(Qt::WA_OpaquePaintEvent); } - _inner.setFont(_st.font->f); - _inner.setAlignment(_st.textAlign); + _inner->setFont(_st.font->f); + _inner->setAlignment(_st.textAlign); - _placeholder = _st.font->elided(_placeholderFull, width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1); + createPlaceholderPath(); QPalette p(palette()); p.setColor(QPalette::Text, _st.textFg->c); setPalette(p); - _inner.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - _inner.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _inner->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _inner->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - _inner.setFrameStyle(QFrame::NoFrame | QFrame::Plain); - _inner.viewport()->setAutoFillBackground(false); + _inner->setFrameStyle(QFrame::NoFrame | QFrame::Plain); + _inner->viewport()->setAutoFillBackground(false); - _inner.setContentsMargins(0, 0, 0, 0); - _inner.document()->setDocumentMargin(0); + _inner->setContentsMargins(0, 0, 0, 0); + _inner->document()->setDocumentMargin(0); setAttribute(Qt::WA_AcceptTouchEvents); - _inner.viewport()->setAttribute(Qt::WA_AcceptTouchEvents); + _inner->viewport()->setAttribute(Qt::WA_AcceptTouchEvents); _touchTimer.setSingleShot(true); connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); - connect(_inner.document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(onDocumentContentsChange(int,int,int))); - connect(_inner.document(), SIGNAL(contentsChanged()), this, SLOT(onDocumentContentsChanged())); - connect(&_inner, SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool))); - connect(&_inner, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool))); - if (App::wnd()) connect(&_inner, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu())); + connect(_inner->document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(onDocumentContentsChange(int,int,int))); + connect(_inner->document(), SIGNAL(contentsChanged()), this, SLOT(onDocumentContentsChanged())); + connect(_inner, SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool))); + connect(_inner, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool))); + if (App::wnd()) connect(_inner, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu())); setCursor(style::cur_text); if (!val.isEmpty()) { - _inner.setPlainText(val); + _inner->setPlainText(val); } - _inner.document()->clearUndoRedoStacks(); + _inner->document()->clearUndoRedoStacks(); + + startPlaceholderAnimation(); + startBorderAnimation(); + finishAnimations(); } void InputField::onTouchTimer() { @@ -2531,69 +2540,102 @@ void InputField::paintEvent(QPaintEvent *e) { p.fillRect(r, _st.textBg); } if (_st.border) { - p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg->b); + p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg); } - if (_st.borderActive && a_borderOpacityActive.current() > 0) { - auto borderFgActive = anim::color(_st.borderFg, _st.borderFgActive, a_borderFgActive.current()); - auto borderFg = anim::color(borderFgActive, _st.borderFgError, a_borderFgError.current()); - p.setOpacity(a_borderOpacityActive.current()); - p.fillRect(0, height() - _st.borderActive, width(), _st.borderActive, borderFg); - p.setOpacity(1); + auto errorDegree = _a_error.current(ms, _error ? 1. : 0.); + auto focusedDegree = _a_focused.current(ms, hasFocus() ? 1. : 0.); + auto borderShownDegree = _a_borderShown.current(ms, 1.); + auto borderOpacity = _a_borderOpacity.current(ms, _borderVisible ? 1. : 0.); + if (_st.borderActive && (borderOpacity > 0.)) { + auto borderStart = snap(_borderAnimationStart, 0, width()); + auto borderFrom = qRound(borderStart * (1. - borderShownDegree)); + auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree); + if (borderTo > borderFrom) { + auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree); + p.setOpacity(borderOpacity); + p.fillRect(borderFrom, height() - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg); + p.setOpacity(1); + } } - auto placeholderOpacity = _a_placeholderVisible.current(ms, _placeholderVisible ? 1. : 0.); - if (placeholderOpacity > 0.) { - p.setOpacity(placeholderOpacity); + if (_st.placeholderScale > 0. && !_placeholderPath.isEmpty()) { + auto placeholderShiftDegree = _a_placeholderShifted.current(ms, _placeholderShifted ? 1. : 0.); p.save(); p.setClipRect(r); - auto placeholderLeft = anim::interpolate(_st.placeholderShift, 0, placeholderOpacity); - + auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree); QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins)); - r.moveLeft(r.left() + placeholderLeft); + r.moveTop(r.top() + placeholderTop); if (rtl()) r.moveLeft(width() - r.left() - r.width()); - p.setFont(_st.font); - p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, _a_placeholderFocused.current(ms, hasFocus() ? 1. : 0.))); - p.drawText(r, _placeholder, _st.placeholderAlign); + auto placeholderScale = 1. - (1. - _st.placeholderScale) * placeholderShiftDegree; + auto placeholderFg = anim::color(_st.placeholderFg, _st.placeholderFgActive, focusedDegree); + placeholderFg = anim::color(placeholderFg, _st.placeholderFgError, errorDegree); + + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(placeholderFg); + p.translate(r.topLeft()); + p.scale(placeholderScale, placeholderScale); + p.drawPath(_placeholderPath); p.restore(); + } else if (!_placeholder.isEmpty()) { + auto placeholderHiddenDegree = _a_placeholderShifted.current(ms, _placeholderShifted ? 1. : 0.); + if (placeholderHiddenDegree < 1.) { + p.setOpacity(1. - placeholderHiddenDegree); + p.save(); + p.setClipRect(r); + + auto placeholderLeft = anim::interpolate(0, -_st.placeholderShift, placeholderHiddenDegree); + + QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins)); + r.moveLeft(r.left() + placeholderLeft); + if (rtl()) r.moveLeft(width() - r.left() - r.width()); + + p.setFont(_st.font); + p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree)); + p.drawText(r, _placeholder, _st.placeholderAlign); + + p.restore(); + } } TWidget::paintEvent(e); } -void InputField::startBorderAnimation() { - a_borderFgActive.start(_focused ? 1. : 0.); - a_borderFgError.start(_error ? 1. : 0.); - a_borderOpacityActive.start((_error || _focused) ? 1 : 0); - _a_border.start(); -} - void InputField::focusInEvent(QFocusEvent *e) { - QTimer::singleShot(0, &_inner, SLOT(setFocus())); + _borderAnimationStart = (e->reason() == Qt::MouseFocusReason) ? mapFromGlobal(QCursor::pos()).x() : (width() / 2); + QTimer::singleShot(0, this, SLOT(onFocusInner())); } void InputField::mousePressEvent(QMouseEvent *e) { - QTimer::singleShot(0, &_inner, SLOT(setFocus())); + _borderAnimationStart = e->pos().x(); + QTimer::singleShot(0, this, SLOT(onFocusInner())); +} + +void InputField::onFocusInner() { + auto borderStart = _borderAnimationStart; + _inner->setFocus(); + _borderAnimationStart = borderStart; } void InputField::contextMenuEvent(QContextMenuEvent *e) { - _inner.contextMenuEvent(e); + _inner->contextMenuEvent(e); } void InputField::Inner::focusInEvent(QFocusEvent *e) { - f()->focusInInner(); + f()->focusInInner(e->reason() == Qt::MouseFocusReason); QTextEdit::focusInEvent(e); emit f()->focused(); } -void InputField::focusInInner() { +void InputField::focusInInner(bool focusByMouse) { if (!_focused) { _focused = true; - - _a_placeholderFocused.start([this] { update(); }, 0., 1., _st.duration); - + _borderAnimationStart = focusByMouse ? mapFromGlobal(QCursor::pos()).x() : (width() / 2); + _a_focused.start([this] { update(); }, 0., 1., _st.duration); + startPlaceholderAnimation(); startBorderAnimation(); } } @@ -2607,13 +2649,36 @@ void InputField::Inner::focusOutEvent(QFocusEvent *e) { void InputField::focusOutInner() { if (_focused) { _focused = false; - - _a_placeholderFocused.start([this] { update(); }, 1., 0., _st.duration); - + _a_focused.start([this] { update(); }, 1., 0., _st.duration); + startPlaceholderAnimation(); startBorderAnimation(); } } +void InputField::startPlaceholderAnimation() { + auto placeholderShifted = (_focused && _st.placeholderScale > 0.) || !getLastText().isEmpty() || _forcePlaceholderHidden; + if (_placeholderShifted != placeholderShifted) { + _placeholderShifted = placeholderShifted; + _a_placeholderShifted.start([this] { update(); }, _placeholderShifted ? 0. : 1., _placeholderShifted ? 1. : 0., _st.duration); + } +} + +void InputField::startBorderAnimation() { + auto borderVisible = (_error || _focused); + if (_borderVisible != borderVisible) { + _borderVisible = borderVisible; + if (_borderVisible) { + if (_a_borderOpacity.animating()) { + _a_borderOpacity.start([this] { update(); }, 0., 1., _st.duration); + } else { + _a_borderShown.start([this] { update(); }, 0., 1., _st.duration); + } + } else { + _a_borderOpacity.start([this] { update(); }, 1., 0., _st.duration); + } + } +} + QSize InputField::sizeHint() const { return geometry().size(); } @@ -2628,7 +2693,7 @@ QString InputField::getText(int32 start, int32 end) const { if (start < 0) start = 0; bool full = (start == 0) && (end < 0); - QTextDocument *doc(_inner.document()); + QTextDocument *doc(_inner->document()); QTextBlock from = full ? doc->begin() : doc->findBlock(start), till = (end < 0) ? doc->end() : doc->findBlock(end); if (till.isValid()) till = till.next(); @@ -2700,7 +2765,7 @@ QString InputField::getText(int32 start, int32 end) const { } bool InputField::hasText() const { - QTextDocument *doc(_inner.document()); + QTextDocument *doc(_inner->document()); QTextBlock from = doc->begin(), till = doc->end(); if (from == till) return false; @@ -2749,11 +2814,11 @@ void InputField::processDocumentContentsChange(int position, int charsAdded) { bool newlineFound = false; static QString regular = qsl("Open Sans"), semibold = qsl("Open Sans Semibold"), space(' '); - bool checkTilde = !cRetina() && (_inner.font().pixelSize() == 13) && (_inner.font().family() == regular); + bool checkTilde = !cRetina() && (_inner->font().pixelSize() == 13) && (_inner->font().family() == regular); bool wasTildeFragment = false; - QTextDocument *doc(_inner.document()); - QTextCursor c(_inner.textCursor()); + QTextDocument *doc(_inner->document()); + QTextCursor c(_inner->textCursor()); c.joinPreviousEditBlock(); while (true) { int32 start = position, end = position + charsAdded; @@ -2832,8 +2897,8 @@ void InputField::processDocumentContentsChange(int position, int charsAdded) { } } if (replacePosition >= 0) { - if (!_inner.document()->pageSize().isNull()) { - _inner.document()->setPageSize(QSizeF(0, 0)); + if (!_inner->document()->pageSize().isNull()) { + _inner->document()->setPageSize(QSizeF(0, 0)); } QTextCursor c(doc->docHandle(), replacePosition); c.setPosition(replacePosition + replaceLen, QTextCursor::KeepAnchor); @@ -2866,10 +2931,10 @@ void InputField::onDocumentContentsChange(int position, int charsRemoved, int ch if (_correcting) return; QString oldtext(_oldtext); - QTextCursor(_inner.document()->docHandle(), 0).joinPreviousEditBlock(); + QTextCursor(_inner->document()->docHandle(), 0).joinPreviousEditBlock(); if (!position) { // Qt bug workaround https://bugreports.qt.io/browse/QTBUG-49062 - QTextCursor c(_inner.document()->docHandle(), 0); + QTextCursor c(_inner->document()->docHandle(), 0); c.movePosition(QTextCursor::End); if (position + charsAdded > c.position()) { int32 toSubstract = position + charsAdded - c.position(); @@ -2882,7 +2947,7 @@ void InputField::onDocumentContentsChange(int position, int charsRemoved, int ch _correcting = true; if (_maxLength >= 0) { - QTextCursor c(_inner.document()->docHandle(), 0); + QTextCursor c(_inner->document()->docHandle(), 0); c.movePosition(QTextCursor::End); int32 fullSize = c.position(), toRemove = fullSize - _maxLength; if (toRemove > 0) { @@ -2908,9 +2973,9 @@ void InputField::onDocumentContentsChange(int position, int charsRemoved, int ch } _correcting = false; - QTextCursor(_inner.document()->docHandle(), 0).endEditBlock(); + QTextCursor(_inner->document()->docHandle(), 0).endEditBlock(); - if (_inner.document()->availableRedoSteps() > 0) return; + if (_inner->document()->availableRedoSteps() > 0) return; const int takeBack = 3; @@ -2923,10 +2988,10 @@ void InputField::onDocumentContentsChange(int position, int charsRemoved, int ch if (charsAdded <= 0) return; _correcting = true; - QSizeF s = _inner.document()->pageSize(); + QSizeF s = _inner->document()->pageSize(); processDocumentContentsChange(position, charsAdded); - if (_inner.document()->pageSize() != s) { - _inner.document()->setPageSize(s); + if (_inner->document()->pageSize() != s) { + _inner->document()->setPageSize(s); } _correcting = false; } @@ -2934,17 +2999,14 @@ void InputField::onDocumentContentsChange(int position, int charsRemoved, int ch void InputField::onDocumentContentsChanged() { if (_correcting) return; - if (_error) { - _error = false; - startBorderAnimation(); - } + setErrorShown(false); - QString curText(getText()); + auto curText = getText(); if (_oldtext != curText) { _oldtext = curText; emit changed(); } - updatePlaceholder(); + startPlaceholderAnimation(); if (App::wnd()) App::wnd()->updateGlobalMenu(); } @@ -2959,50 +3021,31 @@ void InputField::onRedoAvailable(bool avail) { } void InputField::selectAll() { - QTextCursor c(_inner.textCursor()); - c.setPosition(0); - c.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); - _inner.setTextCursor(c); + auto cursor = _inner->textCursor(); + cursor.setPosition(0); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + _inner->setTextCursor(cursor); } -void InputField::finishPlaceholderAnimation() { - _a_placeholderVisible.finish(); - _a_placeholderFocused.finish(); +void InputField::finishAnimations() { + _a_focused.finish(); + _a_error.finish(); + _a_placeholderShifted.finish(); + _a_borderShown.finish(); + _a_borderOpacity.finish(); update(); } -void InputField::step_border(float64 ms, bool timer) { - float64 dt = ms / _st.duration; - if (dt >= 1) { - _a_border.stop(); - a_borderFgActive.finish(); - a_borderFgError.finish(); - a_borderOpacityActive.finish(); - } else { - a_borderFgActive.update(dt, anim::linear); - a_borderFgError.update(dt, anim::linear); - a_borderOpacityActive.update(dt, anim::linear); - } - if (timer) update(); -} - -void InputField::updatePlaceholder() { - auto placeholderVisible = _oldtext.isEmpty(); - if (_placeholderVisible != placeholderVisible) { - _placeholderVisible = placeholderVisible; - _a_placeholderVisible.start([this] { update(); }, _placeholderVisible ? 0. : 1., _placeholderVisible ? 1. : 0., _st.duration); - } -} - void InputField::setPlaceholderHidden(bool forcePlaceholderHidden) { _forcePlaceholderHidden = forcePlaceholderHidden; - updatePlaceholder(); + startPlaceholderAnimation(); } QMimeData *InputField::Inner::createMimeDataFromSelection() const { - QMimeData *result = new QMimeData(); - QTextCursor c(textCursor()); - int32 start = c.selectionStart(), end = c.selectionEnd(); + auto result = new QMimeData(); + auto cursor = textCursor(); + auto start = cursor.selectionStart(); + auto end = cursor.selectionEnd(); if (end > start) { result->setText(f()->getText(start, end)); } @@ -3080,10 +3123,6 @@ void InputField::Inner::keyPressEvent(QKeyEvent *e) { } } -void InputField::Inner::paintEvent(QPaintEvent *e) { - return QTextEdit::paintEvent(e); -} - void InputField::Inner::contextMenuEvent(QContextMenuEvent *e) { if (auto menu = createStandardContextMenu()) { (new Ui::PopupMenu(menu))->popup(e->globalPos()); @@ -3091,51 +3130,58 @@ void InputField::Inner::contextMenuEvent(QContextMenuEvent *e) { } void InputField::resizeEvent(QResizeEvent *e) { - _placeholder = _st.font->elided(_placeholderFull, width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1); - _inner.setGeometry(rect().marginsRemoved(_st.textMargins)); + createPlaceholderPath(); + _inner->setGeometry(rect().marginsRemoved(_st.textMargins)); + _borderAnimationStart = width() / 2; TWidget::resizeEvent(e); } -void InputField::showError() { - _error = true; - if (hasFocus()) { - startBorderAnimation(); +void InputField::createPlaceholderPath() { + auto availableWidth = width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1; + if (_st.placeholderScale > 0.) { + auto placeholderFont = _st.placeholderFont->f; + placeholderFont.setStyleStrategy(QFont::PreferMatch); + auto metrics = QFontMetrics(placeholderFont); + _placeholder = metrics.elidedText(_placeholderFull, Qt::ElideRight, availableWidth); + _placeholderPath = QPainterPath(); + if (!_placeholder.isEmpty()) { + _placeholderPath.addText(0, QFontMetrics(placeholderFont).ascent(), placeholderFont, _placeholder); + } } else { - _inner.setFocus(); + _placeholder = _st.placeholderFont->elided(_placeholderFull, availableWidth); + } +} + +void InputField::showError() { + setErrorShown(true); + if (!hasFocus()) { + _inner->setFocus(); + } +} + +void InputField::setErrorShown(bool error) { + if (_error != error) { + _error = error; + _a_error.start([this] { update(); }, _error ? 0. : 1., _error ? 1. : 0., _st.duration); + startBorderAnimation(); } } MaskedInputField::MaskedInputField(QWidget *parent, const style::InputField &st, const QString &placeholder, const QString &val) : QLineEdit(val, parent) , _st(st) -, _maxLength(-1) , _oldtext(val) +, _placeholderFull(placeholder) { + resize(_st.width, _st.heightMin); -, _undoAvailable(false) -, _redoAvailable(false) - -, _customUpDown(false) - -, _placeholderFull(placeholder) -, _placeholderVisible(val.isEmpty()) -, _placeholderFast(false) - -, _a_border(animation(this, &MaskedInputField::step_border)) - -, _focused(false) -, _error(false) - -, _touchPress(false) -, _touchRightButton(false) -, _touchMove(false) { - resize(_st.width, _st.height); - - setFont(_st.font->f); + setFont(_st.font); setAlignment(_st.textAlign); QPalette p(palette()); p.setColor(QPalette::Text, _st.textFg->c); setPalette(p); + createPlaceholderPath(); + setAttribute(Qt::WA_OpaquePaintEvent); connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(onTextChange(const QString&))); @@ -3153,7 +3199,10 @@ MaskedInputField::MaskedInputField(QWidget *parent, const style::InputField &st, connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); setTextMargins(_st.textMargins); - updatePlaceholder(); + + startPlaceholderAnimation(); + startBorderAnimation(); + finishAnimations(); } void MaskedInputField::setCorrectedText(QString &now, int &nowCursor, const QString &newText, int newPos) { @@ -3164,7 +3213,7 @@ void MaskedInputField::setCorrectedText(QString &now, int &nowCursor, const QStr if (updateText) { now = newText; setText(now); - updatePlaceholder(); + startPlaceholderAnimation(); } auto updateCursorPosition = (newPos != nowCursor) || updateText; if (updateCursorPosition) { @@ -3179,7 +3228,7 @@ void MaskedInputField::customUpDown(bool custom) { void MaskedInputField::setTextMargins(const QMargins &mrg) { _textMargins = mrg; - _placeholder = _st.font->elided(_placeholderFull, width() - _textMargins.left() - _textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1); + createPlaceholderPath(); } void MaskedInputField::onTouchTimer() { @@ -3243,38 +3292,102 @@ QRect MaskedInputField::getTextRect() const { void MaskedInputField::paintEvent(QPaintEvent *e) { Painter p(this); - QRect r(rect().intersected(e->rect())); + auto ms = getms(); + auto r = rect().intersected(e->rect()); p.fillRect(r, _st.textBg); if (_st.border) { p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg->b); } - if (_st.borderActive && a_borderOpacityActive.current() > 0) { - auto borderFgActive = anim::color(_st.borderFg, _st.borderFgActive, a_borderFgActive.current()); - auto borderFg = anim::color(borderFgActive, _st.borderFgError, a_borderFgError.current()); - p.setOpacity(a_borderOpacityActive.current()); - p.fillRect(0, height() - _st.borderActive, width(), _st.borderActive, borderFg); - p.setOpacity(1); + auto errorDegree = _a_error.current(ms, _error ? 1. : 0.); + auto focusedDegree = _a_focused.current(ms, hasFocus() ? 1. : 0.); + auto borderShownDegree = _a_borderShown.current(ms, 1.); + auto borderOpacity = _a_borderOpacity.current(ms, _borderVisible ? 1. : 0.); + if (_st.borderActive && (borderOpacity > 0.)) { + auto borderStart = snap(_borderAnimationStart, 0, width()); + auto borderFrom = qRound(borderStart * (1. - borderShownDegree)); + auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree); + if (borderTo > borderFrom) { + auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree); + p.setOpacity(borderOpacity); + p.fillRect(borderFrom, height() - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg); + p.setOpacity(1); + } } p.setClipRect(r); - paintPlaceholder(p, getms()); + if (_st.placeholderScale > 0. && !_placeholderPath.isEmpty()) { + auto placeholderShiftDegree = _a_placeholderShifted.current(ms, _placeholderShifted ? 1. : 0.); + p.save(); + p.setClipRect(r); + auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree); + + QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins)); + r.moveTop(r.top() + placeholderTop); + if (rtl()) r.moveLeft(width() - r.left() - r.width()); + + auto placeholderScale = 1. - (1. - _st.placeholderScale) * placeholderShiftDegree; + auto placeholderFg = anim::color(_st.placeholderFg, _st.placeholderFgActive, focusedDegree); + placeholderFg = anim::color(placeholderFg, _st.placeholderFgError, errorDegree); + + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(placeholderFg); + p.translate(r.topLeft()); + p.scale(placeholderScale, placeholderScale); + p.drawPath(_placeholderPath); + + p.restore(); + } else if (!_placeholder.isEmpty()) { + auto placeholderHiddenDegree = _a_placeholderShifted.current(ms, _placeholderShifted ? 1. : 0.); + if (placeholderHiddenDegree < 1.) { + p.setOpacity(1. - placeholderHiddenDegree); + p.save(); + p.setClipRect(r); + + auto placeholderLeft = anim::interpolate(0, -_st.placeholderShift, placeholderHiddenDegree); + + QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins)); + r.moveLeft(r.left() + placeholderLeft); + if (rtl()) r.moveLeft(width() - r.left() - r.width()); + + p.setFont(_st.font); + p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree)); + p.drawText(r, _placeholder, _st.placeholderAlign); + + p.restore(); + } + } + + paintAdditionalPlaceholder(p, ms); QLineEdit::paintEvent(e); } void MaskedInputField::startBorderAnimation() { - a_borderFgActive.start(_focused ? 1. : 0.); - a_borderFgError.start(_error ? 1. : 0.); - a_borderOpacityActive.start((_error || _focused) ? 1 : 0); - _a_border.start(); + auto borderVisible = (_error || _focused); + if (_borderVisible != borderVisible) { + _borderVisible = borderVisible; + if (_borderVisible) { + if (_a_borderOpacity.animating()) { + _a_borderOpacity.start([this] { update(); }, 0., 1., _st.duration); + } else { + _a_borderShown.start([this] { update(); }, 0., 1., _st.duration); + } + } else if (qFuzzyCompare(_a_borderShown.current(1.), 0.)) { + _a_borderShown.finish(); + _a_borderOpacity.finish(); + } else { + _a_borderOpacity.start([this] { update(); }, 1., 0., _st.duration); + } + } } void MaskedInputField::focusInEvent(QFocusEvent *e) { if (!_focused) { _focused = true; - - _a_placeholderFocused.start([this] { update(); }, 0., 1., _st.duration); - + _borderAnimationStart = (e->reason() == Qt::MouseFocusReason) ? mapFromGlobal(QCursor::pos()).x() : (width() / 2); + _a_focused.start([this] { update(); }, 0., 1., _st.duration); + startPlaceholderAnimation(); startBorderAnimation(); } QLineEdit::focusInEvent(e); @@ -3284,9 +3397,8 @@ void MaskedInputField::focusInEvent(QFocusEvent *e) { void MaskedInputField::focusOutEvent(QFocusEvent *e) { if (_focused) { _focused = false; - - _a_placeholderFocused.start([this] { update(); }, 1., 0., _st.duration); - + _a_focused.start([this] { update(); }, 1., 0., _st.duration); + startPlaceholderAnimation(); startBorderAnimation(); } QLineEdit::focusOutEvent(e); @@ -3294,13 +3406,25 @@ void MaskedInputField::focusOutEvent(QFocusEvent *e) { } void MaskedInputField::resizeEvent(QResizeEvent *e) { - updatePlaceholderText(); + createPlaceholderPath(); + _borderAnimationStart = width() / 2; QLineEdit::resizeEvent(e); } -void MaskedInputField::updatePlaceholderText() { - _placeholder = _st.font->elided(_placeholderFull, width() - _textMargins.left() - _textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1); - update(); +void MaskedInputField::createPlaceholderPath() { + auto availableWidth = width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1; + if (_st.placeholderScale > 0.) { + auto placeholderFont = _st.placeholderFont->f; + placeholderFont.setStyleStrategy(QFont::PreferMatch); + auto metrics = QFontMetrics(placeholderFont); + _placeholder = metrics.elidedText(_placeholderFull, Qt::ElideRight, availableWidth); + _placeholderPath = QPainterPath(); + if (!_placeholder.isEmpty()) { + _placeholderPath.addText(0, QFontMetrics(placeholderFont).ascent(), placeholderFont, _placeholder); + } + } else { + _placeholder = _st.placeholderFont->elided(_placeholderFull, availableWidth); + } } void MaskedInputField::contextMenuEvent(QContextMenuEvent *e) { @@ -3310,14 +3434,20 @@ void MaskedInputField::contextMenuEvent(QContextMenuEvent *e) { } void MaskedInputField::showError() { - _error = true; - if (hasFocus()) { - startBorderAnimation(); - } else { + setErrorShown(true); + if (!hasFocus()) { setFocus(); } } +void MaskedInputField::setErrorShown(bool error) { + if (_error != error) { + _error = error; + _a_error.start([this] { update(); }, _error ? 0. : 1., _error ? 1. : 0., _st.duration); + startBorderAnimation(); + } +} + QSize MaskedInputField::sizeHint() const { return geometry().size(); } @@ -3326,46 +3456,25 @@ QSize MaskedInputField::minimumSizeHint() const { return geometry().size(); } -void MaskedInputField::step_border(float64 ms, bool timer) { - float64 dt = ms / _st.duration; - if (dt >= 1) { - _a_border.stop(); - a_borderFgActive.finish(); - a_borderFgError.finish(); - a_borderOpacityActive.finish(); - } else { - a_borderFgActive.update(dt, anim::linear); - a_borderFgError.update(dt, anim::linear); - a_borderOpacityActive.update(dt, anim::linear); - } - if (timer) update(); +void MaskedInputField::finishAnimations() { + _a_focused.finish(); + _a_error.finish(); + _a_placeholderShifted.finish(); + _a_borderShown.finish(); + _a_borderOpacity.finish(); + update(); } -bool MaskedInputField::setPlaceholder(const QString &placeholder) { - if (_placeholderFull != placeholder) { - _placeholderFull = placeholder; - updatePlaceholderText(); - return true; - } - return false; +void MaskedInputField::setPlaceholderHidden(bool forcePlaceholderHidden) { + _forcePlaceholderHidden = forcePlaceholderHidden; + startPlaceholderAnimation(); } -void MaskedInputField::setPlaceholderFast(bool fast) { - _placeholderFast = fast; - if (_placeholderFast) { - _a_placeholderVisible.finish(); - update(); - } -} - -void MaskedInputField::updatePlaceholder() { - auto placeholderVisible = _oldtext.isEmpty(); - if (_placeholderVisible != placeholderVisible) { - _placeholderVisible = placeholderVisible; - _a_placeholderVisible.start([this] { update(); }, _placeholderVisible ? 0. : 1., _placeholderVisible ? 1. : 0., _st.duration); - if (_placeholderFast) { - _a_placeholderVisible.finish(); - } +void MaskedInputField::startPlaceholderAnimation() { + auto placeholderShifted = (_focused && _st.placeholderScale > 0.) || !getLastText().isEmpty() || _forcePlaceholderHidden; + if (_placeholderShifted != placeholderShifted) { + _placeholderShifted = placeholderShifted; + _a_placeholderShifted.start([this] { update(); }, _placeholderShifted ? 0. : 1., _placeholderShifted ? 1. : 0., _st.duration); } } @@ -3377,28 +3486,9 @@ QRect MaskedInputField::placeholderRect() const { return rect().marginsRemoved(_st.textMargins + _st.placeholderMargins); } -void MaskedInputField::paintPlaceholder(Painter &p, TimeMs ms) { - auto placeholderOpacity = _a_placeholderVisible.current(ms, _placeholderVisible ? 1. : 0.); - if (placeholderOpacity > 0.) { - p.setOpacity(placeholderOpacity); - p.save(); - - auto placeholderLeft = anim::interpolate(_st.placeholderShift, 0, placeholderOpacity); - - QRect phRect(placeholderRect()); - phRect.moveLeft(phRect.left() + placeholderLeft); - if (rtl()) phRect.moveLeft(width() - phRect.left() - phRect.width()); - - placeholderPreparePaint(p, ms); - p.drawText(phRect, _placeholder, _st.placeholderAlign); - - p.restore(); - } -} - -void MaskedInputField::placeholderPreparePaint(Painter &p, TimeMs ms) { +void MaskedInputField::placeholderAdditionalPrepare(Painter &p, TimeMs ms) { p.setFont(_st.font); - p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, _a_placeholderFocused.current(ms, hasFocus() ? 1. : 0.))); + p.setPen(_st.placeholderFg); } void MaskedInputField::keyPressEvent(QKeyEvent *e) { @@ -3420,7 +3510,7 @@ void MaskedInputField::keyPressEvent(QKeyEvent *e) { _oldtext = newText; _oldcursor = newCursor; if (wasText != _oldtext) emit changed(); - updatePlaceholder(); + startPlaceholderAnimation(); } if (e->key() == Qt::Key_Escape) { e->ignore(); @@ -3445,17 +3535,14 @@ void MaskedInputField::onTextEdited() { _oldtext = newText; _oldcursor = newCursor; if (wasText != _oldtext) emit changed(); - updatePlaceholder(); + startPlaceholderAnimation(); if (App::wnd()) App::wnd()->updateGlobalMenu(); } void MaskedInputField::onTextChange(const QString &text) { _oldtext = QLineEdit::text(); - if (_error) { - _error = false; - startBorderAnimation(); - } + setErrorShown(false); if (App::wnd()) App::wnd()->updateGlobalMenu(); } @@ -3523,25 +3610,23 @@ void CountryCodeInput::correctValue(const QString &was, int32 wasCursor, QString } } -PhonePartInput::PhonePartInput(QWidget *parent, const style::InputField &st) : MaskedInputField(parent, st, lang(lng_phone_ph)) { +PhonePartInput::PhonePartInput(QWidget *parent, const style::InputField &st) : MaskedInputField(parent, st/*, lang(lng_phone_ph)*/) { } -void PhonePartInput::paintPlaceholder(Painter &p, TimeMs ms) { +void PhonePartInput::paintAdditionalPlaceholder(Painter &p, TimeMs ms) { auto t = getLastText(); - if (!_pattern.isEmpty() && !t.isEmpty()) { - auto ph = placeholder().mid(t.size()); + if (!_pattern.isEmpty()) { + auto ph = _additionalPlaceholder.mid(t.size()); if (!ph.isEmpty()) { p.setClipRect(rect()); auto phRect = placeholderRect(); int tw = phFont()->width(t); if (tw < phRect.width()) { phRect.setLeft(phRect.left() + tw); - placeholderPreparePaint(p, ms); + placeholderAdditionalPrepare(p, ms); p.drawText(phRect, ph, style::al_topleft); } } - } else { - MaskedInputField::paintPlaceholder(p, ms); } } @@ -3622,7 +3707,7 @@ void PhonePartInput::addedToNumber(const QString &added) { setText(newText); setCursorPosition(added.length()); correctValue(wasText, wasCursor, newText, newCursor); - updatePlaceholder(); + startPlaceholderAnimation(); } void PhonePartInput::onChooseCode(const QString &code) { @@ -3632,24 +3717,23 @@ void PhonePartInput::onChooseCode(const QString &code) { } else { _pattern.clear(); } - if (_pattern.isEmpty()) { - setPlaceholder(lang(lng_phone_ph)); - } else { - QString ph; - ph.reserve(20); + _additionalPlaceholder = QString(); + if (!_pattern.isEmpty()) { + _additionalPlaceholder.reserve(20); for (int i = 0, l = _pattern.size(); i < l; ++i) { - ph.append(' '); - ph.append(QString(_pattern.at(i), QChar(0x2212))); + _additionalPlaceholder.append(' '); + _additionalPlaceholder.append(QString(_pattern.at(i), QChar(0x2212))); } - setPlaceholder(ph); } + setPlaceholderHidden(!_additionalPlaceholder.isEmpty()); + auto wasText = getLastText(); auto wasCursor = cursorPosition(); auto newText = getLastText(); auto newCursor = newText.size(); correctValue(wasText, wasCursor, newText, newCursor); - setPlaceholderFast(!_pattern.isEmpty()); - updatePlaceholder(); + + startPlaceholderAnimation(); } PasswordInput::PasswordInput(QWidget *parent, const style::InputField &st, const QString &ph, const QString &val) : MaskedInputField(parent, st, ph, val) { @@ -3687,13 +3771,12 @@ UsernameInput::UsernameInput(QWidget *parent, const style::InputField &st, const _linkPlaceholder(isLink ? qsl("telegram.me/") : QString()) { if (!_linkPlaceholder.isEmpty()) { setTextMargins(style::margins(_st.textMargins.left() + _st.font->width(_linkPlaceholder), _st.textMargins.top(), _st.textMargins.right(), _st.textMargins.bottom())); + setPlaceholderHidden(true); } } -void UsernameInput::paintPlaceholder(Painter &p, TimeMs ms) { - if (_linkPlaceholder.isEmpty()) { - MaskedInputField::paintPlaceholder(p, ms); - } else { +void UsernameInput::paintAdditionalPlaceholder(Painter &p, TimeMs ms) { + if (!_linkPlaceholder.isEmpty()) { p.setFont(_st.font); p.setPen(_st.placeholderFg); p.drawText(QRect(_st.textMargins.left(), _st.textMargins.top(), width(), height() - _st.textMargins.top() - _st.textMargins.bottom()), _linkPlaceholder, style::al_topleft); @@ -3721,8 +3804,7 @@ void UsernameInput::correctValue(const QString &was, int32 wasCursor, QString &n setCorrectedText(now, nowCursor, now.mid(from, len), newPos); } -PhoneInput::PhoneInput(QWidget *parent, const style::InputField &st, const QString &ph, const QString &val) : MaskedInputField(parent, st, ph, val) -, _defaultPlaceholder(ph) { +PhoneInput::PhoneInput(QWidget *parent, const style::InputField &st, const QString &ph, const QString &val) : MaskedInputField(parent, st, ph, val) { QString phone(val); if (phone.isEmpty()) { clearText(); @@ -3750,22 +3832,20 @@ void PhoneInput::clearText() { correctValue(QString(), 0, phone, pos); } -void PhoneInput::paintPlaceholder(Painter &p, TimeMs ms) { - auto t = getLastText(); - if (!_pattern.isEmpty() && !t.isEmpty()) { - auto ph = placeholder().mid(t.size()); +void PhoneInput::paintAdditionalPlaceholder(Painter &p, TimeMs ms) { + if (!_pattern.isEmpty()) { + auto t = getLastText(); + auto ph = _additionalPlaceholder.mid(t.size()); if (!ph.isEmpty()) { p.setClipRect(rect()); auto phRect = placeholderRect(); int tw = phFont()->width(t); if (tw < phRect.width()) { phRect.setLeft(phRect.left() + tw); - placeholderPreparePaint(p, ms); + placeholderAdditionalPrepare(p, ms); p.drawText(phRect, ph, style::al_topleft); } } - } else { - MaskedInputField::paintPlaceholder(p, ms); } } @@ -3776,7 +3856,7 @@ void PhoneInput::correctValue(const QString &was, int32 wasCursor, QString &now, QString newPlaceholder; if (_pattern.isEmpty()) { - newPlaceholder = lang(lng_contact_phone); + newPlaceholder = QString(); } else if (_pattern.size() == 1 && _pattern.at(0) == digits.size()) { newPlaceholder = QString(_pattern.at(0) + 2, ' ') + lang(lng_contact_phone); } else { @@ -3790,9 +3870,10 @@ void PhoneInput::correctValue(const QString &was, int32 wasCursor, QString &now, newPlaceholder.append(i ? QString(_pattern.at(i), QChar(0x2212)) : digits.mid(0, _pattern.at(i))); } } - if (setPlaceholder(newPlaceholder)) { - setPlaceholderFast(!_pattern.isEmpty()); - updatePlaceholder(); + if (_additionalPlaceholder != newPlaceholder) { + _additionalPlaceholder = newPlaceholder; + setPlaceholderHidden(!_additionalPlaceholder.isEmpty()); + update(); } QString newText; diff --git a/Telegram/SourceFiles/ui/widgets/input_fields.h b/Telegram/SourceFiles/ui/widgets/input_fields.h index 7e5f3a1a7..557fc8fce 100644 --- a/Telegram/SourceFiles/ui/widgets/input_fields.h +++ b/Telegram/SourceFiles/ui/widgets/input_fields.h @@ -247,8 +247,6 @@ class FlatInput : public QLineEdit { public: FlatInput(QWidget *parent, const style::FlatInput &st, const QString &ph = QString(), const QString &val = QString()); - void setPlaceholder(const QString &ph); - void setPlaceholderFast(bool fast); void updatePlaceholder(); const QString &placeholder() const; QRect placeholderRect() const; @@ -309,7 +307,6 @@ private: void updatePlaceholderText(); QString _oldtext, _ph, _fullph; - bool _fastph = false; bool _customUpDown = false; @@ -324,17 +321,17 @@ private: QPoint _touchStart; }; -enum CtrlEnterSubmit { - CtrlEnterSubmitEnter, - CtrlEnterSubmitCtrlEnter, - CtrlEnterSubmitBoth, +enum class CtrlEnterSubmit { + Enter, + CtrlEnter, + Both, }; class InputArea : public TWidget { Q_OBJECT public: - InputArea(QWidget *parent, const style::InputArea &st, const QString &ph = QString(), const QString &val = QString()); + InputArea(QWidget *parent, const style::InputField &st, const QString &ph = QString(), const QString &val = QString()); void showError(); @@ -345,9 +342,7 @@ public: const QString &getLastText() const { return _oldtext; } - void updatePlaceholder(); - - void step_border(float64 ms, bool timer); + void finishAnimations(); QSize sizeHint() const override; QSize minimumSizeHint() const override; @@ -362,30 +357,30 @@ public: void setCtrlEnterSubmit(CtrlEnterSubmit ctrlEnterSubmit); void setTextCursor(const QTextCursor &cursor) { - return _inner.setTextCursor(cursor); + return _inner->setTextCursor(cursor); } QTextCursor textCursor() const { - return _inner.textCursor(); + return _inner->textCursor(); } void setText(const QString &text) { - _inner.setText(text); - updatePlaceholder(); + _inner->setText(text); + startPlaceholderAnimation(); } void clear() { - _inner.clear(); - updatePlaceholder(); + _inner->clear(); + startPlaceholderAnimation(); } bool hasFocus() const { - return _inner.hasFocus(); + return _inner->hasFocus(); } void setFocus() { - _inner.setFocus(); + _inner->setFocus(); } void clearFocus() { - _inner.clearFocus(); + _inner->clearFocus(); } -public slots: +private slots: void onTouchTimer(); void onDocumentContentsChange(int position, int charsRemoved, int charsAdded); @@ -394,6 +389,8 @@ public slots: void onUndoAvailable(bool avail); void onRedoAvailable(bool avail); + void onFocusInner(); + signals: void changed(); void submitted(bool ctrlShiftEnter); @@ -405,6 +402,9 @@ signals: void resized(); protected: + void startPlaceholderAnimation(); + void startBorderAnimation(); + void insertEmoji(EmojiPtr emoji, QTextCursor c); TWidget *tparent() { return qobject_cast(parentWidget()); @@ -421,10 +421,6 @@ protected: void resizeEvent(QResizeEvent *e) override; private: - int32 _maxLength; - bool heightAutoupdated(); - void checkContentHeight(); - class Inner : public QTextEdit { public: Inner(InputArea *parent); @@ -436,7 +432,6 @@ private: void focusInEvent(QFocusEvent *e) override; void focusOutEvent(QFocusEvent *e) override; void keyPressEvent(QKeyEvent *e) override; - void paintEvent(QPaintEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; QMimeData *createMimeDataFromSelection() const override; @@ -450,41 +445,55 @@ private: }; friend class Inner; - void focusInInner(); + bool heightAutoupdated(); + void checkContentHeight(); + void createPlaceholderPath(); + void setErrorShown(bool error); + + void focusInInner(bool focusByMouse); void focusOutInner(); void processDocumentContentsChange(int position, int charsAdded); - void startBorderAnimation(); + const style::InputField &_st; - Inner _inner; + int _maxLength = -1; + + ChildWidget _inner; QString _oldtext; - CtrlEnterSubmit _ctrlEnterSubmit; - bool _undoAvailable, _redoAvailable, _inHeightCheck; + CtrlEnterSubmit _ctrlEnterSubmit = CtrlEnterSubmit::CtrlEnter; + bool _undoAvailable = false; + bool _redoAvailable = false; + bool _inHeightCheck = false; - bool _customUpDown; + bool _customUpDown = false; - QString _placeholder, _placeholderFull; - bool _placeholderVisible; - Animation _a_placeholderFocused; - Animation _a_placeholderVisible; + QString _placeholder; + QString _placeholderFull; + Animation _a_placeholderShifted; + bool _placeholderShifted = false; + QPainterPath _placeholderPath; - anim::value a_borderOpacityActive; - anim::value a_borderFgActive; - anim::value a_borderFgError; - BasicAnimation _a_border; + Animation _a_borderShown; + int _borderAnimationStart = 0; + Animation _a_borderOpacity; + bool _borderVisible = false; - bool _focused, _error; + Animation _a_focused; + Animation _a_error; - const style::InputArea &_st; + bool _focused = false; + bool _error = false; QTimer _touchTimer; - bool _touchPress, _touchRightButton, _touchMove; + bool _touchPress = false; + bool _touchRightButton = false; + bool _touchMove = false; QPoint _touchStart; - bool _correcting; + bool _correcting = false; }; @@ -503,11 +512,8 @@ public: const QString &getLastText() const { return _oldtext; } - void updatePlaceholder(); void setPlaceholderHidden(bool forcePlaceholderHidden); - void finishPlaceholderAnimation(); - - void step_border(float64 ms, bool timer); + void finishAnimations(); QSize sizeHint() const override; QSize minimumSizeHint() const override; @@ -521,38 +527,41 @@ public: void customUpDown(bool isCustom); void setTextCursor(const QTextCursor &cursor) { - return _inner.setTextCursor(cursor); + return _inner->setTextCursor(cursor); } QTextCursor textCursor() const { - return _inner.textCursor(); + return _inner->textCursor(); } void setText(const QString &text) { - _inner.setText(text); - updatePlaceholder(); + _inner->setText(text); + startPlaceholderAnimation(); } void clear() { - _inner.clear(); - updatePlaceholder(); + _inner->clear(); + startPlaceholderAnimation(); } bool hasFocus() const { - return _inner.hasFocus(); + return _inner->hasFocus(); } void setFocus() { - _inner.setFocus(); - QTextCursor c(_inner.textCursor()); - c.movePosition(QTextCursor::End); - _inner.setTextCursor(c); + _inner->setFocus(); + auto cursor = _inner->textCursor(); + cursor.movePosition(QTextCursor::End); + _inner->setTextCursor(cursor); } void clearFocus() { - _inner.clearFocus(); + _inner->clearFocus(); } void setCursorPosition(int pos) { - QTextCursor c(_inner.textCursor()); - c.setPosition(pos); - _inner.setTextCursor(c); + auto cursor = _inner->textCursor(); + cursor.setPosition(pos); + _inner->setTextCursor(cursor); } public slots: + void selectAll(); + +private slots: void onTouchTimer(); void onDocumentContentsChange(int position, int charsRemoved, int charsAdded); @@ -561,7 +570,7 @@ public slots: void onUndoAvailable(bool avail); void onRedoAvailable(bool avail); - void selectAll(); + void onFocusInner(); signals: void changed(); @@ -573,6 +582,9 @@ signals: void blurred(); protected: + void startPlaceholderAnimation(); + void startBorderAnimation(); + void insertEmoji(EmojiPtr emoji, QTextCursor c); TWidget *tparent() { return qobject_cast(parentWidget()); @@ -589,9 +601,6 @@ protected: void resizeEvent(QResizeEvent *e) override; private: - int32 _maxLength; - bool _forcePlaceholderHidden = false; - class Inner : public QTextEdit { public: Inner(InputField *parent); @@ -603,7 +612,6 @@ private: void focusInEvent(QFocusEvent *e) override; void focusOutEvent(QFocusEvent *e) override; void keyPressEvent(QKeyEvent *e) override; - void paintEvent(QPaintEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; QMimeData *createMimeDataFromSelection() const override; @@ -617,40 +625,52 @@ private: }; friend class Inner; - void focusInInner(); + void createPlaceholderPath(); + void setErrorShown(bool error); + + void focusInInner(bool focusByMouse); void focusOutInner(); void processDocumentContentsChange(int position, int charsAdded); - void startBorderAnimation(); + const style::InputField &_st; - Inner _inner; + int _maxLength = -1; + bool _forcePlaceholderHidden = false; + + ChildWidget _inner; QString _oldtext; - bool _undoAvailable, _redoAvailable; + bool _undoAvailable = false; + bool _redoAvailable = false; - bool _customUpDown; + bool _customUpDown = true; - QString _placeholder, _placeholderFull; - bool _placeholderVisible; - Animation _a_placeholderFocused; - Animation _a_placeholderVisible; + QString _placeholder; + QString _placeholderFull; + Animation _a_placeholderShifted; + bool _placeholderShifted = false; + QPainterPath _placeholderPath; - anim::value a_borderOpacityActive; - anim::value a_borderFgActive; - anim::value a_borderFgError; - BasicAnimation _a_border; + Animation _a_borderShown; + int _borderAnimationStart = 0; + Animation _a_borderOpacity; + bool _borderVisible = false; - bool _focused, _error; + Animation _a_focused; + Animation _a_error; - const style::InputField &_st; + bool _focused = false; + bool _error = false; QTimer _touchTimer; - bool _touchPress, _touchRightButton, _touchMove; + bool _touchPress = false; + bool _touchRightButton = false; + bool _touchMove = false; QPoint _touchStart; - bool _correcting; + bool _correcting = false; }; class MaskedInputField : public QLineEdit { @@ -662,14 +682,8 @@ public: void showError(); - bool setPlaceholder(const QString &ph); - void setPlaceholderFast(bool fast); - void updatePlaceholder(); - QRect getTextRect() const; - void step_border(float64 ms, bool timer); - QSize sizeHint() const override; QSize minimumSizeHint() const override; @@ -677,13 +691,16 @@ public: const QString &getLastText() const { return _oldtext; } + void setPlaceholderHidden(bool forcePlaceholderHidden); + void finishAnimations(); + void setText(const QString &text) { QLineEdit::setText(text); - updatePlaceholder(); + startPlaceholderAnimation(); } void clear() { QLineEdit::clear(); - updatePlaceholder(); + startPlaceholderAnimation(); } QMargins getMargins() const { @@ -706,6 +723,9 @@ signals: void blurred(); protected: + void startBorderAnimation(); + void startPlaceholderAnimation(); + bool event(QEvent *e) override; void touchEvent(QTouchEvent *e); void paintEvent(QPaintEvent *e) override; @@ -726,13 +746,14 @@ protected: } void setCorrectedText(QString &now, int &nowCursor, const QString &newText, int newPos); - virtual void paintPlaceholder(Painter &p, TimeMs ms); + virtual void paintAdditionalPlaceholder(Painter &p, TimeMs ms) { + } style::font phFont() { return _st.font; } - void placeholderPreparePaint(Painter &p, TimeMs ms); + void placeholderAdditionalPrepare(Painter &p, TimeMs ms); const QString &placeholder() const; QRect placeholderRect() const; @@ -740,34 +761,43 @@ protected: const style::InputField &_st; private: - void startBorderAnimation(); - void updatePlaceholderText(); + void createPlaceholderPath(); + void setErrorShown(bool error); - int32 _maxLength; + int _maxLength = -1; + bool _forcePlaceholderHidden = false; QString _oldtext; - int32 _oldcursor; + int _oldcursor = 0; - bool _undoAvailable, _redoAvailable; + bool _undoAvailable = false; + bool _redoAvailable = false; - bool _customUpDown; + bool _customUpDown = false; - QString _placeholder, _placeholderFull; - bool _placeholderVisible, _placeholderFast; - Animation _a_placeholderFocused; - Animation _a_placeholderVisible; + QString _placeholder; + QString _placeholderFull; + Animation _a_placeholderShifted; + bool _placeholderShifted = false; + QPainterPath _placeholderPath; - anim::value a_borderOpacityActive; - anim::value a_borderFgActive; - anim::value a_borderFgError; - BasicAnimation _a_border; + Animation _a_borderShown; + int _borderAnimationStart = 0; + Animation _a_borderOpacity; + bool _borderVisible = false; - bool _focused, _error; + Animation _a_focused; + Animation _a_error; + + bool _focused = false; + bool _error = false; style::margins _textMargins; QTimer _touchTimer; - bool _touchPress, _touchRightButton, _touchMove; + bool _touchPress = false; + bool _touchRightButton = false; + bool _touchMove = false; QPoint _touchStart; }; @@ -810,10 +840,11 @@ protected: void keyPressEvent(QKeyEvent *e) override; void correctValue(const QString &was, int32 wasCursor, QString &now, int32 &nowCursor) override; - void paintPlaceholder(Painter &p, TimeMs ms) override; + void paintAdditionalPlaceholder(Painter &p, TimeMs ms) override; private: QVector _pattern; + QString _additionalPlaceholder; }; @@ -838,7 +869,7 @@ public: protected: void correctValue(const QString &was, int32 wasCursor, QString &now, int32 &nowCursor) override; - void paintPlaceholder(Painter &p, TimeMs ms) override; + void paintAdditionalPlaceholder(Painter &p, TimeMs ms) override; private: QString _linkPlaceholder; @@ -855,11 +886,11 @@ protected: void focusInEvent(QFocusEvent *e) override; void correctValue(const QString &was, int32 wasCursor, QString &now, int32 &nowCursor) override; - void paintPlaceholder(Painter &p, TimeMs ms) override; + void paintAdditionalPlaceholder(Painter &p, TimeMs ms) override; private: - QString _defaultPlaceholder; QVector _pattern; + QString _additionalPlaceholder; }; diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.cpp b/Telegram/SourceFiles/ui/widgets/multi_select.cpp index accc210fc..01da8e788 100644 --- a/Telegram/SourceFiles/ui/widgets/multi_select.cpp +++ b/Telegram/SourceFiles/ui/widgets/multi_select.cpp @@ -700,7 +700,7 @@ void MultiSelect::Inner::addItem(std_::unique_ptr item, AddItemWay way) { if (way != AddItemWay::SkipAnimation) { _items.back()->showAnimated(); } else { - _field->finishPlaceholderAnimation(); + _field->finishAnimations(); finishHeightAnimation(); } } diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 852605b1f..19a2e7d68 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -181,16 +181,20 @@ FlatInput { phDuration: int; } -InputArea { +InputField { textBg: color; textFg: color; textMargins: margins; + textAlign: align; placeholderFg: color; placeholderFgActive: color; + placeholderFgError: color; placeholderMargins: margins; placeholderAlign: align; + placeholderScale: double; placeholderShift: pixels; + placeholderFont: font; duration: int; @@ -209,34 +213,6 @@ InputArea { heightMax: pixels; } -InputField { - textBg: color; - textFg: color; - textMargins: margins; - textAlign: align; - - placeholderFg: color; - placeholderFgActive: color; - placeholderMargins: margins; - placeholderAlign: align; - placeholderShift: pixels; - - duration: int; - - borderFg: color; - borderFgActive: color; - borderFgError: color; - - border: pixels; - borderActive: pixels; - borderError: pixels; - - font: font; - - width: pixels; - height: pixels; -} - OutlineButton { outlineWidth: pixels; outlineFg: color; @@ -602,44 +578,21 @@ attentionLeftOutlineButton: OutlineButton(defaultLeftOutlineButton) { } } -defaultInputArea: InputArea { - textBg: windowBg; - textFg: windowFg; - textMargins: margins(5px, 6px, 5px, 4px); - - placeholderFg: #999999; - placeholderFgActive: #aaaaaa; - placeholderMargins: margins(2px, 0px, 2px, 0px); - placeholderAlign: align(topleft); - placeholderShift: 50px; - duration: 120; - - borderFg: #e0e0e0; - borderFgActive: activeLineFg; - borderFgError: #e48383; - - border: 1px; - borderActive: 2px; - borderError: 2px; - - font: boxTextFont; - - heightMin: 32px; - heightMax: 128px; -} - defaultInputField: InputField { textBg: windowBg; textFg: windowFg; - textMargins: margins(0px, 6px, 0px, 4px); + textMargins: margins(0px, 26px, 0px, 4px); textAlign: align(topleft); - placeholderFg: #999999; - placeholderFgActive: #aaaaaa; - placeholderMargins: margins(2px, 0px, 2px, 0px); + placeholderFg: windowSubTextFg; + placeholderFgActive: windowActiveTextFg; + placeholderFgError: attentionButtonFg; + placeholderMargins: margins(0px, 0px, 0px, 0px); placeholderAlign: align(topleft); - placeholderShift: 50px; - duration: 120; + placeholderScale: 0.9; + placeholderShift: -20px; + placeholderFont: font(semibold 14px); + duration: 150; borderFg: #e0e0e0; borderFgActive: activeLineFg; @@ -651,7 +604,8 @@ defaultInputField: InputField { font: boxTextFont; - height: 32px; + heightMin: 52px; + heightMax: 148px; } defaultCheckboxIcon: icon {{ "default_checkbox_check", windowFgActive, point(4px, 7px) }}; @@ -909,3 +863,10 @@ MediaPlayerButton { rippleAreaSize: pixels; ripple: RippleAnimation; } + +ProfilePeerListItem { + button: OutlineButton; + statusFg: color; + statusFgOver: color; + statusFgActive: color; +} diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index 166c18640..3d7d0181e 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -731,7 +731,7 @@ void Notification::showReplyField() { _replyArea->show(); _replyArea->setFocus(); _replyArea->setMaxLength(MaxMessageSize); - _replyArea->setCtrlEnterSubmit(Ui::CtrlEnterSubmitBoth); + _replyArea->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both); // Catch mouse press event to activate the window. Sandbox::installEventFilter(this); diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 1160b21e3..7c1a2b0fa 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -68,11 +68,13 @@ notifyActionsDuration: 200; notifyHideAllHeight: 36px; -notifyReplyArea: InputArea(defaultInputArea) { +notifyReplyArea: InputField(defaultInputField) { font: normalFont; textMargins: margins(8px, 8px, 8px, 6px); heightMin: 36px; heightMax: 72px; + placeholderScale: 0.; + placeholderFont: normalFont; border: 0px; borderActive: 0px; borderError: 0px; diff --git a/Telegram/build/version b/Telegram/build/version index 112e9a899..0b546acd0 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -3,4 +3,4 @@ AppVersionStrMajor 0.10 AppVersionStrSmall 0.10.20 AppVersionStr 0.10.20 AlphaChannel 0 -BetaVersion 10019013 +BetaVersion 10019014