/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_chat.h" #include "settings/settings_common.h" #include "settings/settings_advanced.h" #include "boxes/connection_box.h" #include "boxes/auto_download_box.h" #include "boxes/stickers_box.h" #include "ui/boxes/confirm_box.h" #include "boxes/background_box.h" #include "boxes/background_preview_box.h" #include "boxes/download_path_box.h" #include "boxes/local_storage_box.h" #include "boxes/edit_color_box.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/chat/attach/attach_extensions.h" #include "ui/chat/chat_theme.h" #include "ui/layers/generic_box.h" #include "ui/effects/radial_animation.h" #include "ui/style/style_palette_colorizer.h" #include "ui/toast/toast.h" #include "ui/image/image.h" #include "ui/ui_utility.h" #include "lang/lang_keys.h" #include "export/export_manager.h" #include "window/themes/window_theme.h" #include "window/themes/window_themes_embedded.h" #include "window/themes/window_theme_editor_box.h" #include "window/themes/window_themes_cloud_list.h" #include "window/window_adaptive.h" #include "window/window_session_controller.h" #include "window/window_controller.h" #include "info/downloads/info_downloads_widget.h" #include "info/info_memento.h" #include "storage/localstorage.h" #include "core/file_utilities.h" #include "core/application.h" #include "data/data_session.h" #include "data/data_cloud_themes.h" #include "data/data_file_origin.h" #include "chat_helpers/emoji_sets_manager.h" #include "base/platform/base_platform_info.h" #include "platform/platform_specific.h" #include "base/call_delayed.h" #include "support/support_common.h" #include "support/support_templates.h" #include "main/main_session.h" #include "main/main_session_settings.h" #include "mainwidget.h" #include "mainwindow.h" #include "facades.h" #include "styles/style_settings.h" #include "styles/style_layers.h" #include "styles/style_window.h" namespace Settings { namespace { const auto kSchemesList = Window::Theme::EmbeddedThemes(); constexpr auto kCustomColorButtonParts = 7; class ColorsPalette final { public: using Type = Window::Theme::EmbeddedType; using Scheme = Window::Theme::EmbeddedScheme; explicit ColorsPalette(not_null container); void show(Type type); rpl::producer selected() const; private: class Button { public: Button( not_null parent, std::vector &&colors, bool selected); void moveToLeft(int x, int y); void update(std::vector &&colors, bool selected); rpl::producer<> clicks() const; bool selected() const; QColor color() const; private: void paint(); Ui::AbstractButton _widget; std::vector _colors; Ui::Animations::Simple _selectedAnimation; bool _selected = false; }; void show( not_null scheme, std::vector &&colors, int selected); void selectCustom(not_null scheme); void updateInnerGeometry(); not_null*> _outer; std::vector> _buttons; rpl::event_stream _selected; }; void PaintColorButton(Painter &p, QColor color, float64 selected) { const auto size = st::settingsAccentColorSize; const auto rect = QRect(0, 0, size, size); p.setBrush(color); p.setPen(Qt::NoPen); p.drawEllipse(rect); if (selected > 0.) { const auto startSkip = -st::settingsAccentColorLine / 2.; const auto endSkip = float64(st::settingsAccentColorSkip); const auto skip = startSkip + (endSkip - startSkip) * selected; auto pen = st::boxBg->p; pen.setWidth(st::settingsAccentColorLine); p.setBrush(Qt::NoBrush); p.setPen(pen); p.setOpacity(selected); p.drawEllipse(QRectF(rect).marginsRemoved({ skip, skip, skip, skip })); } } void PaintCustomButton(Painter &p, const std::vector &colors) { Expects(colors.size() >= kCustomColorButtonParts); p.setPen(Qt::NoPen); const auto size = st::settingsAccentColorSize; const auto smallSize = size / 8.; const auto drawAround = [&](QPointF center, int index) { const auto where = QPointF{ size * (1. + center.x()) / 2, size * (1. + center.y()) / 2 }; p.setBrush(colors[index]); p.drawEllipse( where.x() - smallSize, where.y() - smallSize, 2 * smallSize, 2 * smallSize); }; drawAround(QPointF(), 0); for (auto i = 0; i != 6; ++i) { const auto angle = i * M_PI / 3.; const auto point = QPointF{ cos(angle), sin(angle) }; const auto adjusted = point * (1. - (2 * smallSize / size)); drawAround(adjusted, i + 1); } } ColorsPalette::Button::Button( not_null parent, std::vector &&colors, bool selected) : _widget(parent.get()) , _colors(std::move(colors)) , _selected(selected) { _widget.show(); _widget.resize(st::settingsAccentColorSize, st::settingsAccentColorSize); _widget.paintRequest( ) | rpl::start_with_next([=] { paint(); }, _widget.lifetime()); } void ColorsPalette::Button::moveToLeft(int x, int y) { _widget.moveToLeft(x, y); } void ColorsPalette::Button::update( std::vector &&colors, bool selected) { if (_colors != colors) { _colors = std::move(colors); _widget.update(); } if (_selected != selected) { _selected = selected; _selectedAnimation.start( [=] { _widget.update(); }, _selected ? 0. : 1., _selected ? 1. : 0., st::defaultRadio.duration * 2); } } rpl::producer<> ColorsPalette::Button::clicks() const { return _widget.clicks() | rpl::to_empty; } bool ColorsPalette::Button::selected() const { return _selected; } QColor ColorsPalette::Button::color() const { Expects(_colors.size() == 1); return _colors.front(); } void ColorsPalette::Button::paint() { Painter p(&_widget); PainterHighQualityEnabler hq(p); if (_colors.size() == 1) { PaintColorButton( p, _colors.front(), _selectedAnimation.value(_selected ? 1. : 0.)); } else if (_colors.size() >= kCustomColorButtonParts) { PaintCustomButton(p, _colors); } } ColorsPalette::ColorsPalette(not_null container) : _outer(container->add( object_ptr>( container, object_ptr(container)))) { _outer->hide(anim::type::instant); const auto inner = _outer->entity(); inner->widthValue( ) | rpl::start_with_next([=] { updateInnerGeometry(); }, inner->lifetime()); } void ColorsPalette::show(Type type) { const auto scheme = ranges::find(kSchemesList, type, &Scheme::type); if (scheme == end(kSchemesList)) { _outer->hide(anim::type::instant); return; } auto list = Window::Theme::DefaultAccentColors(type); if (list.empty()) { _outer->hide(anim::type::instant); return; } list.insert(list.begin(), scheme->accentColor); const auto color = Core::App().settings().themesAccentColors().get(type); const auto current = color.value_or(scheme->accentColor); const auto i = ranges::find(list, current); if (i == end(list)) { list.back() = current; } const auto selected = std::clamp( int(i - begin(list)), 0, int(list.size()) - 1); _outer->show(anim::type::instant); show(&*scheme, std::move(list), selected); const auto inner = _outer->entity(); inner->resize(_outer->width(), inner->height()); updateInnerGeometry(); } void ColorsPalette::show( not_null scheme, std::vector &&colors, int selected) { Expects(selected >= 0 && selected < colors.size()); while (_buttons.size() > colors.size()) { _buttons.pop_back(); } auto index = 0; const auto inner = _outer->entity(); const auto pushButton = [&](std::vector &&colors) { auto result = rpl::producer<>(); const auto chosen = (index == selected); if (_buttons.size() > index) { _buttons[index]->update(std::move(colors), chosen); } else { _buttons.push_back(std::make_unique