diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 4674270cd..62c13da7d 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -857,6 +857,8 @@ PRIVATE info/profile/info_profile_widget.h info/settings/info_settings_widget.cpp info/settings/info_settings_widget.h + info/userpic/info_userpic_colors_editor.cpp + info/userpic/info_userpic_colors_editor.h info/userpic/info_userpic_emoji_builder.cpp info/userpic/info_userpic_emoji_builder.h info/userpic/info_userpic_emoji_builder_common.cpp diff --git a/Telegram/SourceFiles/info/userpic/info_userpic_builder.style b/Telegram/SourceFiles/info/userpic/info_userpic_builder.style index 8d26cbd5e..c933bb518 100644 --- a/Telegram/SourceFiles/info/userpic/info_userpic_builder.style +++ b/Telegram/SourceFiles/info/userpic/info_userpic_builder.style @@ -48,6 +48,25 @@ userpicBuilderEmojiLayerMinHeight: 496px; userpicBuilderEmojiSelectorMinHeight: 216px; userpicBuilderEmojiSelectorTogglePosition: point(6px, 6px); +userpicBuilderEmojiColorMinus: IconButton(defaultIconButton) { + width: userpicBuilderEmojiAccentColorSize; + height: userpicBuilderEmojiAccentColorSize; + + icon: icon {{ "settings/minus", menuIconFg }}; + iconOver: icon {{ "settings/minus", menuIconFg }}; + + rippleAreaSize: userpicBuilderEmojiAccentColorSize; + rippleAreaPosition: point(0px, 0px); + ripple: universalRippleAnimation; +} + +userpicBuilderEmojiColorPlus: IconButton(userpicBuilderEmojiColorMinus) { + icon: icon {{ "settings/plus", menuIconFg }}; + iconOver: icon {{ "settings/plus", menuIconFg }}; +} + userpicBuilderEmojiToggleEmojiIcon: icon {{ "chat/input_smile_face", emojiIconFg }}; userpicBuilderEmojiToggleEmojiSize: 18px; userpicBuilderEmojiToggleStickersIcon: icon {{ "menu/stickers", emojiIconFg }}; + +userpicBuilderEmojiSlideDuration: 120; diff --git a/Telegram/SourceFiles/info/userpic/info_userpic_colors_editor.cpp b/Telegram/SourceFiles/info/userpic/info_userpic_colors_editor.cpp new file mode 100644 index 000000000..63280d5b4 --- /dev/null +++ b/Telegram/SourceFiles/info/userpic/info_userpic_colors_editor.cpp @@ -0,0 +1,309 @@ +/* +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 "info/userpic/info_userpic_colors_editor.h" + +#include "base/random.h" +#include "ui/wrap/fade_wrap.h" +#include "info/userpic/info_userpic_emoji_builder_preview.h" +#include "info/userpic/info_userpic_color_circle_button.h" +#include "info/userpic/info_userpic_emoji_builder_common.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/widgets/color_editor.h" +#include "ui/wrap/padding_wrap.h" +#include "settings/settings_common.h" +#include "ui/widgets/buttons.h" +#include "ui/rect.h" +#include "styles/style_info_userpic_builder.h" +#include "styles/style_boxes.h" + +namespace UserpicBuilder { +namespace { + +constexpr auto kMaxColors = int(4); + +[[nodiscard]] QColor RandomColor(const QColor &c) { + auto random = bytes::vector(2); + base::RandomFill(random.data(), random.size()); + auto result = QColor(); + result.setHslF( + (uchar(random[0]) % 100) / 100., + (uchar(random[1]) % 50) / 100. + 0.5, + c.lightnessF()); + return result; +} + +class ColorsLine final : public Ui::RpWidget { +public: + using Chosen = CircleButton; + using Success = bool; + ColorsLine( + not_null parent, + not_null*> colors); + + void init(); + void fillButtons(); + Success addColor(); + + [[nodiscard]] Chosen *chosen() const; + [[nodiscard]] rpl::producer chosenChanges() const; + +private: + struct ButtonState { + bool shown = false; + int left = 0; + }; + [[nodiscard]] std::vector calculatePositionFor(int count); + void processChange( + const std::vector wasColors, + const std::vector nowColors); + void setLastChosen() const; + + const not_null*> _colors; + + base::unique_qptr _container; + + std::vector> _colorButtons; + std::vector*>> _wraps; + + Ui::Animations::Simple _chooseAnimation; + Ui::Animations::Simple _positionAnimation; + Chosen *_chosen = nullptr; + + rpl::event_stream _chosenChanges; + +}; + +ColorsLine::ColorsLine( + not_null parent, + not_null*> colors) +: Ui::RpWidget(parent) +, _colors(colors) { +} + +void ColorsLine::init() { + fillButtons(); + processChange(*_colors, *_colors); + setLastChosen(); +} + +ColorsLine::Success ColorsLine::addColor() { +} + +void ColorsLine::fillButtons() { + _container = base::make_unique_q(this); + const auto container = _container.get(); + sizeValue( + ) | rpl::start_with_next([=](const QSize &s) { + container->setGeometry(Rect(s)); + }, container->lifetime()); + + const auto minus = Ui::CreateChild>( + container, + object_ptr( + container, + st::userpicBuilderEmojiColorMinus)); + _wraps.push_back(minus); + minus->entity()->setClickedCallback([=] { + const auto wasColors = *_colors; + _colors->erase(_colors->end() - 1); + const auto nowColors = *_colors; + processChange(wasColors, nowColors); + setLastChosen(); + }); + + for (auto i = 0; i < kMaxColors; i++) { + const auto wrap = Ui::CreateChild>( + container, + object_ptr(container)); + const auto button = wrap->entity(); + button->resize(height(), height()); + button->setIndex(i); + _wraps.push_back(wrap); + _colorButtons.push_back(button); + button->setClickedCallback([=] { + const auto wasChosen = _chosen; + _chosen = button; + const auto nowChosen = _chosen; + _chosenChanges.fire_copy(_chosen); + + _chooseAnimation.stop(); + _chooseAnimation.start([=](float64 progress) { + if (wasChosen) { + wasChosen->setSelectedProgress(1. - progress); + } + nowChosen->setSelectedProgress(progress); + }, 0., 1., st::userpicBuilderEmojiSlideDuration); + }); + if (i < _colors->size()) { + button->setBrush((*_colors)[i]); + } else { + wrap->hide(anim::type::instant); + } + } + + const auto plus = Ui::CreateChild>( + container, + object_ptr( + container, + st::userpicBuilderEmojiColorPlus)); + _wraps.push_back(plus); + plus->entity()->setClickedCallback([=] { + const auto wasColors = *_colors; + _colors->push_back(RandomColor(_colors->back())); + const auto nowColors = *_colors; + processChange(wasColors, nowColors); + setLastChosen(); + }); + for (const auto &wrap : _wraps) { + wrap->setDuration(st::userpicBuilderEmojiSlideDuration); + } +} + +std::vector ColorsLine::calculatePositionFor( + int count) { + // Minus - Color - Color - Color - Color - Plus. + auto result = std::vector(6); + const auto fullWidth = _container->width(); + const auto width = _container->height(); + const auto colorsWidth = width * count + width * (count - 1); + const auto left = (fullWidth - colorsWidth) / 2; + for (auto i = 0; i < _colorButtons.size(); i++) { + result[i + 1] = { + .shown = (i < count), + .left = left + (i * width * 2), + }; + } + result[0] = { + .shown = (count > 1), + .left = (left - width * 2), + }; + result[result.size() - 1] = { + .shown = (count < kMaxColors), + .left = (left + colorsWidth + width), + }; + return result; +} + +void ColorsLine::processChange( + const std::vector wasColors, + const std::vector nowColors) { + const auto wasPosition = calculatePositionFor(wasColors.size()); + const auto nowPosition = calculatePositionFor(nowColors.size()); + for (auto i = 0; i < nowPosition.size(); i++) { + const auto colorIndex = i - 1; + if ((colorIndex > 0) && (colorIndex < _colors->size())) { + _colorButtons[colorIndex]->setBrush((*_colors)[colorIndex]); + } + _wraps[i]->toggle(nowPosition[i].shown, anim::type::normal); + } + _positionAnimation.stop(); + _positionAnimation.start([=](float64 value) { + for (auto i = 0; i < nowPosition.size(); i++) { + const auto wasLeft = wasPosition[i].left; + const auto nowLeft = nowPosition[i].left; + const auto left = anim::interpolate(wasLeft, nowLeft, value); + _wraps[i]->moveToLeft(left, 0); + } + }, 0., 1., st::userpicBuilderEmojiSlideDuration); +} + +void ColorsLine::setLastChosen() const { + for (auto i = 0; i < _colorButtons.size(); i++) { + if (i == (_colors->size() - 1)) { + _colorButtons[i]->clicked({}, Qt::LeftButton); + } + } +} + +ColorsLine::Chosen *ColorsLine::chosen() const { + return _chosen; +} + +rpl::producer ColorsLine::chosenChanges() const { + return _chosenChanges.events(); +} + +} // namespace + +object_ptr CreateGradientEditor( + not_null parent, + DocumentData *document, + std::vector startColors, + BothWayCommunication> communication) { + auto container = object_ptr(parent.get()); + + struct State { + std::vector colors; + }; + const auto preview = container->add( + object_ptr>( + container, + object_ptr( + container, + Size(st::defaultUserpicButton.photoSize))))->entity(); + preview->setDuration(0); + if (document) { + preview->setDocument(document); + } + + Settings::AddSkip(container); + Settings::AddDivider(container); + Settings::AddSkip(container); + + const auto state = container->lifetime().make_state(); + state->colors = std::move(startColors); + const auto buttonsContainer = container->add(object_ptr( + container, + &state->colors)); + buttonsContainer->resize(0, st::userpicBuilderEmojiAccentColorSize); + + Settings::AddSkip(container); + Settings::AddDivider(container); + Settings::AddSkip(container); + + const auto editor = container->add(object_ptr( + container, + ColorEditor::Mode::HSL, + state->colors.back())); + + buttonsContainer->chosenChanges( + ) | rpl::start_with_next([=](ColorsLine::Chosen *chosen) { + if (chosen) { + editor->showColor(state->colors[chosen->index()]); + } + }, editor->lifetime()); + + const auto save = crl::guard(container.data(), [=] { + communication.result(state->colors); + }); + // editor->submitRequests( + // ) | rpl::start_with_next([=] { + // }, editor->lifetime()); + editor->colorValue( + ) | rpl::start_with_next([=](QColor c) { + if (const auto chosen = buttonsContainer->chosen()) { + chosen->setBrush(c); + state->colors[chosen->index()] = c; + } + preview->setGradientColors(state->colors); + }, preview->lifetime()); + + base::take( + communication.triggers + ) | rpl::start_with_next([=] { + save(); + }, container->lifetime()); + + container->resizeToWidth(editor->width()); + buttonsContainer->init(); + + return container; +} + +} // namespace UserpicBuilder + diff --git a/Telegram/SourceFiles/info/userpic/info_userpic_colors_editor.h b/Telegram/SourceFiles/info/userpic/info_userpic_colors_editor.h new file mode 100644 index 000000000..d4eb78095 --- /dev/null +++ b/Telegram/SourceFiles/info/userpic/info_userpic_colors_editor.h @@ -0,0 +1,30 @@ +/* +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 +*/ +#pragma once + +template +class object_ptr; + +class DocumentData; + +namespace Ui { +class RpWidget; +} // namespace Ui + +namespace UserpicBuilder { + +template +struct BothWayCommunication; + +[[nodiscard]] object_ptr CreateGradientEditor( + not_null parent, + DocumentData *document, + std::vector startColors, + BothWayCommunication> communication); + +} // namespace UserpicBuilder diff --git a/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_widget.cpp b/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_widget.cpp index 4095aae25..1735b0504 100644 --- a/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_widget.cpp +++ b/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_widget.cpp @@ -327,7 +327,7 @@ not_null CreateUserpicBuilder( 1. - progress); } state->circleButtons[now]->setSelectedProgress(progress); - }, 0., 1., st::slideDuration); + }, 0., 1., st::userpicBuilderEmojiSlideDuration); state->colorIndex = now; preview->setGradientColors(colors);