diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 130d096c18..153ff4040b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -324,7 +324,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_bg_use_default" = "Use default color theme"; "lng_settings_bg_from_gallery" = "Choose from gallery"; "lng_settings_bg_from_file" = "Choose from file"; -"lng_settings_bg_edit_theme" = "Edit theme"; +"lng_settings_bg_edit_theme" = "Launch theme editor"; "lng_settings_bg_tile" = "Tile background"; "lng_settings_adaptive_wide" = "Adaptive layout for wide screens"; @@ -334,6 +334,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_local_storage" = "Local storage"; "lng_settings_connection_type" = "Connection type"; "lng_settings_downloading_update" = "Downloading update {progress}..."; +"lng_settings_use_night_mode" = "Use night mode"; "lng_backgrounds_header" = "Choose your new chat background"; "lng_theme_sure_keep" = "Keep this theme?"; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 2d756f8988..9962d5bbf8 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -1440,7 +1440,7 @@ void EditPeerInfoBox::prepare() { ) | rpl::start_with_next([this](int height) { setDimensions(st::boxWideWidth, height); }, content->lifetime()); - setInnerWidget(object_ptr( + setInnerWidget(object_ptr( this, std::move(content))); Ui::AttachAsChild(this, std::move(controller)); diff --git a/Telegram/SourceFiles/export/view/export_view_settings.cpp b/Telegram/SourceFiles/export/view/export_view_settings.cpp index 8cc6aa7aaf..7a3c8da687 100644 --- a/Telegram/SourceFiles/export/view/export_view_settings.cpp +++ b/Telegram/SourceFiles/export/view/export_view_settings.cpp @@ -93,9 +93,10 @@ void SettingsWidget::setupContent() { const auto scroll = Ui::CreateChild( this, st::boxLayerScroll); - const auto wrap = scroll->setOwnedWidget(object_ptr( - scroll, - object_ptr(scroll))); + const auto wrap = scroll->setOwnedWidget( + object_ptr( + scroll, + object_ptr(scroll))); const auto content = static_cast(wrap->entity()); const auto buttons = setupButtons(scroll, wrap); diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 2f51ec746d..4a27468c38 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -16,6 +16,9 @@ settingsSectionButton: InfoProfileButton(infoProfileButton) { settingsButton: InfoProfileButton(settingsSectionButton) { padding: margins(28px, 10px, 8px, 8px); } +settingsChatButton: InfoProfileButton(settingsSectionButton) { + padding: margins(22px, 10px, 8px, 8px); +} settingsSectionSkip: infoProfileSkip; settingsButtonRightPosition: point(28px, 10px); settingsButtonRight: FlatLabel(defaultFlatLabel) { @@ -37,7 +40,30 @@ settingsUpdate: InfoProfileButton(infoMainButton, settingsButton) { } settingsUpdateStatePosition: point(28px, 30px); -settingsNotificationsCheckbox: defaultBoxCheckbox; -settingsNotificationsCheckboxPadding: margins(22px, 10px, 10px, 10px); +settingsSendType: defaultBoxCheckbox; +settingsSendTypePadding: margins(22px, 5px, 10px, 5px); +settingsSendTypeSkip: 5px; + +settingsCheckbox: defaultBoxCheckbox; +settingsCheckboxPadding: margins(22px, 10px, 10px, 10px); settingsLink: boxLinkButton; settingsAdvancedNotificationsPadding: margins(22px, 20px, 10px, 10px); +settingsLinkLabel: defaultFlatLabel; + +settingsAskPathPadding: margins(22px, 10px, 10px, 0px); +settingsDownloadPathPadding: margins(22px, 1px, 10px, 0px); + +settingsBackgroundThumb: 76px; +settingsThumbSkip: 16px; +settingsBackgroundTitle: FlatLabel(defaultFlatLabel) { + style: TextStyle(semiboldTextStyle) { + font: font(boxFontSize semibold); + linkFont: font(boxFontSize semibold); + linkFontOver: font(boxFontSize semibold underline); + } + textFg: windowActiveTextFg; +} +settingsBackgroundTitlePadding: margins(22px, 7px, 10px, 9px); +settingsBackgroundPadding: margins(22px, 8px, 10px, 8px); +settingsFromGalleryTop: 2px; +settingsFromFileTop: 14px; diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 8eddbff240..e98bf379fb 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -8,12 +8,686 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_chat.h" #include "settings/settings_common.h" -#include "boxes/abstract_box.h" +#include "boxes/connection_box.h" +#include "boxes/stickers_box.h" +#include "boxes/background_box.h" +#include "boxes/download_path_box.h" #include "ui/wrap/vertical_layout.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/labels.h" +#include "ui/effects/radial_animation.h" #include "lang/lang_keys.h" +#include "window/themes/window_theme_editor.h" +#include "window/themes/window_theme.h" +#include "info/profile/info_profile_button.h" +#include "storage/localstorage.h" +#include "core/file_utilities.h" +#include "mainwidget.h" #include "styles/style_settings.h" namespace Settings { +namespace { + +class BackgroundRow : public Ui::RpWidget { +public: + BackgroundRow(QWidget *parent); + +protected: + void paintEvent(QPaintEvent *e) override; + + int resizeGetHeight(int newWidth) override; + +private: + void updateImage(); + + float64 radialProgress() const; + bool radialLoading() const; + QRect radialRect() const; + void radialStart(); + TimeMs radialTimeShift() const; + void step_radial(TimeMs ms, bool timer); + + QPixmap _background; + object_ptr _chooseFromGallery; + object_ptr _chooseFromFile; + + Ui::RadialAnimation _radial; + +}; + +#ifndef OS_WIN_STORE +class DownloadPathRow : public Ui::RpWidget { +public: + DownloadPathRow(QWidget *parent); + +private: + void setupControls(); + +}; +#endif // OS_WIN_STORE + +void ChooseFromFile(not_null parent); + +BackgroundRow::BackgroundRow(QWidget *parent) : RpWidget(parent) +, _chooseFromGallery( + this, + lang(lng_settings_bg_from_gallery), + st::settingsLink) +, _chooseFromFile(this, lang(lng_settings_bg_from_file), st::settingsLink) +, _radial(animation(this, &BackgroundRow::step_radial)) { + updateImage(); + + _chooseFromGallery->addClickHandler([] { + Ui::show(Box()); + }); + _chooseFromFile->addClickHandler([=] { + ChooseFromFile(this); + }); + + using Update = const Window::Theme::BackgroundUpdate; + base::ObservableViewer( + *Window::Theme::Background() + ) | rpl::filter([](const Update &update) { + return (update.type == Update::Type::New + || update.type == Update::Type::Start + || update.type == Update::Type::Changed); + }) | rpl::start_with_next([=] { + updateImage(); + }, lifetime()); +} + +void BackgroundRow::paintEvent(QPaintEvent *e) { + Painter p(this); + + bool radial = false; + float64 radialOpacity = 0; + if (_radial.animating()) { + _radial.step(getms()); + radial = _radial.animating(); + radialOpacity = _radial.opacity(); + } + if (radial) { + const auto backThumb = App::main()->newBackgroundThumb(); + if (backThumb->isNull()) { + p.drawPixmap(0, 0, _background); + } else { + const auto &pix = backThumb->pixBlurred( + Data::FileOrigin(), + st::settingsBackgroundThumb); + const auto factor = cIntRetinaFactor(); + p.drawPixmap( + 0, + 0, + st::settingsBackgroundThumb, + st::settingsBackgroundThumb, + pix, + 0, + (pix.height() - st::settingsBackgroundThumb * factor) / 2, + st::settingsBackgroundThumb * factor, + st::settingsBackgroundThumb * factor); + } + + const auto outer = radialRect(); + const auto inner = QRect( + QPoint( + outer.x() + (outer.width() - st::radialSize.width()) / 2, + outer.y() + (outer.height() - st::radialSize.height()) / 2), + st::radialSize); + p.setPen(Qt::NoPen); + p.setOpacity(radialOpacity); + p.setBrush(st::radialBg); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + p.setOpacity(1); + const auto arc = inner.marginsRemoved(QMargins( + st::radialLine, + st::radialLine, + st::radialLine, + st::radialLine)); + _radial.draw(p, arc, st::radialLine, st::radialFg); + } else { + p.drawPixmap(0, 0, _background); + } +} + +int BackgroundRow::resizeGetHeight(int newWidth) { + auto linkTop = st::settingsFromGalleryTop; + auto linkLeft = st::settingsBackgroundThumb + st::settingsThumbSkip; + auto linkWidth = newWidth - linkLeft; + _chooseFromGallery->resizeToWidth( + qMin(linkWidth, _chooseFromGallery->naturalWidth())); + _chooseFromFile->resizeToWidth( + qMin(linkWidth, _chooseFromFile->naturalWidth())); + _chooseFromGallery->moveToLeft(linkLeft, linkTop, newWidth); + linkTop += _chooseFromGallery->height() + st::settingsFromFileTop; + _chooseFromFile->moveToLeft(linkLeft, linkTop, newWidth); + return st::settingsBackgroundThumb; +} + +float64 BackgroundRow::radialProgress() const { + return App::main()->chatBackgroundProgress(); +} + +bool BackgroundRow::radialLoading() const { + const auto main = App::main(); + if (main->chatBackgroundLoading()) { + main->checkChatBackground(); + if (main->chatBackgroundLoading()) { + return true; + } else { + const_cast(this)->updateImage(); + } + } + return false; +} + +QRect BackgroundRow::radialRect() const { + return QRect( + 0, + 0, + st::settingsBackgroundThumb, + st::settingsBackgroundThumb); +} + +void BackgroundRow::radialStart() { + if (radialLoading() && !_radial.animating()) { + _radial.start(radialProgress()); + if (const auto shift = radialTimeShift()) { + _radial.update( + radialProgress(), + !radialLoading(), + getms() + shift); + } + } +} + +TimeMs BackgroundRow::radialTimeShift() const { + return st::radialDuration; +} + +void BackgroundRow::step_radial(TimeMs ms, bool timer) { + _radial.update( + radialProgress(), + !radialLoading(), + ms + radialTimeShift()); + if (timer && _radial.animating()) { + rtlupdate(radialRect()); + } +} + +void BackgroundRow::updateImage() { + int32 size = st::settingsBackgroundThumb * cIntRetinaFactor(); + QImage back(size, size, QImage::Format_ARGB32_Premultiplied); + back.setDevicePixelRatio(cRetinaFactor()); + { + Painter p(&back); + PainterHighQualityEnabler hq(p); + + const auto &pix = Window::Theme::Background()->pixmap(); + const auto sx = (pix.width() > pix.height()) + ? ((pix.width() - pix.height()) / 2) + : 0; + const auto sy = (pix.height() > pix.width()) + ? ((pix.height() - pix.width()) / 2) + : 0; + const auto s = (pix.width() > pix.height()) + ? pix.height() + : pix.width(); + p.drawPixmap( + 0, + 0, + st::settingsBackgroundThumb, + st::settingsBackgroundThumb, + pix, + sx, + sy, + s, + s); + } + Images::prepareRound(back, ImageRoundRadius::Small); + _background = App::pixmapFromImageInPlace(std::move(back)); + _background.setDevicePixelRatio(cRetinaFactor()); + + rtlupdate(radialRect()); + + if (radialLoading()) { + radialStart(); + } +} + +void ChooseFromFile(not_null parent) { + const auto imgExtensions = cImgExtensions(); + auto filters = QStringList( + qsl("Theme files (*.tdesktop-theme *.tdesktop-palette *") + + imgExtensions.join(qsl(" *")) + + qsl(")")); + filters.push_back(FileDialog::AllFilesFilter()); + const auto callback = [=](const FileDialog::OpenResult &result) { + if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { + return; + } + + if (!result.paths.isEmpty()) { + const auto filePath = result.paths.front(); + const auto hasExtension = [&](QLatin1String extension) { + return filePath.endsWith(extension, Qt::CaseInsensitive); + }; + if (hasExtension(qstr(".tdesktop-theme")) + || hasExtension(qstr(".tdesktop-palette"))) { + Window::Theme::Apply(filePath); + return; + } + } + + auto image = result.remoteContent.isEmpty() + ? App::readImage(result.paths.front()) + : App::readImage(result.remoteContent); + if (image.isNull() || image.width() <= 0 || image.height() <= 0) { + return; + } else if (image.width() > 4096 * image.height()) { + image = image.copy( + (image.width() - 4096 * image.height()) / 2, + 0, + 4096 * image.height(), + image.height()); + } else if (image.height() > 4096 * image.width()) { + image = image.copy( + 0, + (image.height() - 4096 * image.width()) / 2, + image.width(), + 4096 * image.width()); + } + + Window::Theme::Background()->setImage( + Window::Theme::kCustomBackground, + std::move(image)); + Window::Theme::Background()->setTile(false); + }; + FileDialog::GetOpenPath( + parent.get(), + lang(lng_choose_image), + filters.join(qsl(";;")), + crl::guard(parent, callback)); + +} + +#ifndef OS_WIN_STORE + +QString DownloadPathText() { + if (Global::DownloadPath().isEmpty()) { + return lang(lng_download_path_default); + } else if (Global::DownloadPath() == qsl("tmp")) { + return lang(lng_download_path_temp); + } + return QDir::toNativeSeparators(Global::DownloadPath()); +} + +DownloadPathRow::DownloadPathRow(QWidget *parent) : RpWidget(parent) { + setupControls(); +} + +void DownloadPathRow::setupControls() { + const auto label = Ui::CreateChild( + this, + Lang::Viewer(lng_download_path_label), + st::settingsLinkLabel); + const auto link = Ui::CreateChild( + this, + DownloadPathText(), + st::settingsLink); + + base::ObservableViewer( + Global::RefDownloadPathChanged() + ) | rpl::map([] { + return DownloadPathText(); + }) | rpl::start_with_next([=](const QString &text) { + link->setText(text); + }, link->lifetime()); + + link->addClickHandler([] { + Ui::show(Box()); + }); + + rpl::combine( + widthValue(), + label->sizeValue(), + link->widthValue() + ) | rpl::start_with_next([=](int width, QSize labelSize, int possible) { + const auto space = st::settingsLinkLabel.style.font->spacew; + link->resizeToNaturalWidth(width - labelSize.width() - space); + if (link->width() == possible) { + label->moveToLeft(0, 0); + link->moveToLeft(labelSize.width() + space, 0); + resize(width, labelSize.height()); + } + }, link->lifetime()); +} + +#endif // OS_WIN_STORE + +void SetupChatOptions(not_null container) { + AddDivider(container); + AddSkip(container); + + auto wrap = object_ptr(container); + const auto inner = wrap.data(); + container->add(object_ptr( + container, + std::move(wrap), + QMargins(0, 0, 0, st::settingsCheckbox.margin.bottom()))); + + const auto checkbox = [&](LangKey label, bool checked) { + return object_ptr( + container, + lang(label), + checked, + st::settingsCheckbox); + }; + const auto add = [&](LangKey label, bool checked, auto &&handle) { + base::ObservableViewer( + inner->add( + checkbox(label, checked), + st::settingsCheckboxPadding + )->checkedChanged + ) | rpl::start_with_next( + std::move(handle), + inner->lifetime()); + }; + add( + lng_settings_replace_emojis, + Global::ReplaceEmoji(), + [](bool checked) { + Global::SetReplaceEmoji(checked); + Global::RefReplaceEmojiChanged().notify(); + Local::writeUserSettings(); + }); + + add( + lng_settings_suggest_emoji, + Global::SuggestEmoji(), + [](bool checked) { + Global::SetSuggestEmoji(checked); + Local::writeUserSettings(); + }); + + add( + lng_settings_suggest_by_emoji, + Global::SuggestStickersByEmoji(), + [](bool checked) { + Global::SetSuggestStickersByEmoji(checked); + Local::writeUserSettings(); + }); + + const auto dontask = inner->add( + checkbox( + lng_download_path_dont_ask, + !Global::AskDownloadPath()), +#ifndef OS_WIN_STORE + st::settingsAskPathPadding); +#else // OS_WIN_STORE + st::settingsCheckboxPadding); +#endif // OS_WIN_STORE + +#ifndef OS_WIN_STORE + const auto showpath = Ui::AttachAsChild( + dontask, + rpl::event_stream()); + const auto padding = st::settingsDownloadPathPadding; + const auto path = container->add( + object_ptr>( + container, + object_ptr(container), + QMargins( + (padding.left() + + st::settingsCheckbox.checkPosition.x() + + st::defaultCheck.diameter + + st::settingsCheckbox.textPosition.x() + - st::settingsCheckbox.margin.left()), + padding.top(), + padding.right(), + padding.bottom()))); + AddSkip(container, st::settingsCheckboxPadding.bottom()); + path->toggleOn( + showpath->events_starting_with(!Global::AskDownloadPath())); +#endif // OS_WIN_STORE + + base::ObservableViewer( + dontask->checkedChanged + ) | rpl::start_with_next([=](bool checked) { + Global::SetAskDownloadPath(!checked); + Local::writeUserSettings(); + +#ifndef OS_WIN_STORE + showpath->fire_copy(checked); +#endif // OS_WIN_STORE + + }, inner->lifetime()); + + AddSkip(container); +} + +void SetupSendKey(not_null container) { + AddDivider(container); + const auto skip = st::settingsSendTypeSkip; + const auto full = st::settingsSectionSkip + skip; + AddSkip(container, full); + + enum class SendByType { + Enter, + CtrlEnter, + }; + + const auto group = std::make_shared>( + cCtrlEnter() ? SendByType::CtrlEnter : SendByType::Enter); + auto wrap = object_ptr(container); + const auto inner = wrap.data(); + container->add( + object_ptr( + container, + std::move(wrap), + QMargins(0, skip, 0, skip))); + + AddSkip(container, full); + + const auto add = [&]( + SendByType value, + LangKey key, + style::margins padding) { + inner->add( + object_ptr>( + inner, + group, + value, + lang(key), + st::settingsSendType), + st::settingsSendTypePadding); + }; + const auto small = st::settingsSendTypePadding; + const auto top = skip; + add( + SendByType::Enter, + lng_settings_send_enter, + { small.left(), small.top() + top, small.right(), small.bottom() }); + add( + SendByType::CtrlEnter, + ((cPlatform() == dbipMac || cPlatform() == dbipMacOld) + ? lng_settings_send_cmdenter + : lng_settings_send_ctrlenter), + { small.left(), small.top(), small.right(), small.bottom() + top }); + group->setChangedCallback([](SendByType value) { + cSetCtrlEnter(value == SendByType::CtrlEnter); + if (App::main()) { + App::main()->ctrlEnterSubmitUpdated(); + } + Local::writeUserSettings(); + }); +} + +void SetupMediaOptions(not_null container) { + AddDivider(container); + AddSkip(container); + + AddButton( + container, + lng_media_auto_settings, + st::settingsChatButton + )->addClickHandler([] { + Ui::show(Box()); + }); + + AddButton( + container, + lng_stickers_you_have, + st::settingsChatButton + )->addClickHandler([] { + Ui::show(Box(StickersBox::Section::Installed)); + }); + + AddSkip(container); +} + +void SetupChatBackground(not_null container) { + AddDivider(container); + AddSkip(container); + + container->add( + object_ptr( + container, + Lang::Viewer(lng_settings_section_background), + st::settingsBackgroundTitle), + st::settingsBackgroundTitlePadding); + + container->add( + object_ptr(container), + st::settingsBackgroundPadding); + + const auto skipTop = st::settingsCheckbox.margin.top(); + const auto skipBottom = st::settingsCheckbox.margin.bottom(); + auto wrap = object_ptr(container); + const auto inner = wrap.data(); + container->add( + object_ptr( + container, + std::move(wrap), + QMargins(0, skipTop, 0, skipBottom))); + + AddSkip(container); + + const auto tile = inner->add( + object_ptr( + inner, + lang(lng_settings_bg_tile), + Window::Theme::Background()->tile(), + st::settingsCheckbox), + st::settingsSendTypePadding); + const auto adaptive = inner->add( + object_ptr>( + inner, + object_ptr( + inner, + lang(lng_settings_adaptive_wide), + Global::AdaptiveForWide(), + st::settingsCheckbox), + st::settingsSendTypePadding)); + + base::ObservableViewer( + tile->checkedChanged + ) | rpl::start_with_next([](bool checked) { + Window::Theme::Background()->setTile(checked); + }, tile->lifetime()); + + using Update = const Window::Theme::BackgroundUpdate; + base::ObservableViewer( + *Window::Theme::Background() + ) | rpl::filter([](const Update &update) { + return (update.type == Update::Type::Changed); + }) | rpl::map([] { + return Window::Theme::Background()->tile(); + }) | rpl::start_with_next([=](bool tiled) { + tile->setChecked(tiled); + }, tile->lifetime()); + + adaptive->toggleOn(rpl::single( + rpl::empty_value() + ) | rpl::then(base::ObservableViewer( + Adaptive::Changed() + )) | rpl::map([] { + return (Global::AdaptiveChatLayout() == Adaptive::ChatLayout::Wide); + })); + + base::ObservableViewer( + adaptive->entity()->checkedChanged + ) | rpl::start_with_next([](bool checked) { + Global::SetAdaptiveForWide(checked); + Adaptive::Changed().notify(); + Local::writeUserSettings(); + }, adaptive->lifetime()); +} + +void SetupThemeOptions(not_null container) { + AddDivider(container); + AddSkip(container); + + const auto calling = Ui::AttachAsChild(container, 0); + AddButton( + container, + lng_settings_use_night_mode, + st::settingsChatButton + )->toggleOn( + rpl::single(Window::Theme::IsNightMode()) + )->toggledValue( + ) | rpl::start_with_next([=](bool toggled) { + ++*calling; + const auto change = [=] { + if (!--*calling && toggled != Window::Theme::IsNightMode()) { + Window::Theme::ToggleNightMode(); + } + }; + App::CallDelayed( + st::settingsButton.toggle.duration, + container, + change); + }, container->lifetime()); + + AddButton( + container, + lng_settings_bg_edit_theme, + st::settingsChatButton + )->addClickHandler(App::LambdaDelayed( + st::settingsChatButton.ripple.hideDuration, + container, + [] { Window::Theme::Editor::Start(); })); + + using Update = const Window::Theme::BackgroundUpdate; + container->add( + object_ptr>( + container, + object_ptr