317 lines
8.6 KiB
C++
317 lines
8.6 KiB
C++
/*
|
|
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<Ui::RpWidget*> parent,
|
|
not_null<std::vector<QColor>*> colors);
|
|
|
|
void init();
|
|
void fillButtons();
|
|
|
|
[[nodiscard]] Chosen *chosen() const;
|
|
[[nodiscard]] rpl::producer<Chosen*> chosenChanges() const;
|
|
|
|
private:
|
|
struct ButtonState {
|
|
bool shown = false;
|
|
int left = 0;
|
|
};
|
|
[[nodiscard]] std::vector<ButtonState> calculatePositionFor(int count);
|
|
void processChange(
|
|
const std::vector<QColor> wasColors,
|
|
const std::vector<QColor> nowColors);
|
|
void setLastChosen() const;
|
|
|
|
const not_null<std::vector<QColor>*> _colors;
|
|
|
|
base::unique_qptr<Ui::RpWidget> _container;
|
|
|
|
std::vector<not_null<CircleButton*>> _colorButtons;
|
|
std::vector<not_null<Ui::FadeWrap<Ui::RpWidget>*>> _wraps;
|
|
|
|
Ui::Animations::Simple _chooseAnimation;
|
|
Ui::Animations::Simple _positionAnimation;
|
|
Chosen *_chosen = nullptr;
|
|
|
|
rpl::event_stream<Chosen*> _chosenChanges;
|
|
|
|
};
|
|
|
|
ColorsLine::ColorsLine(
|
|
not_null<Ui::RpWidget*> parent,
|
|
not_null<std::vector<QColor>*> colors)
|
|
: Ui::RpWidget(parent)
|
|
, _colors(colors) {
|
|
}
|
|
|
|
void ColorsLine::init() {
|
|
fillButtons();
|
|
processChange(*_colors, *_colors);
|
|
setLastChosen();
|
|
}
|
|
|
|
void ColorsLine::fillButtons() {
|
|
_container = base::make_unique_q<Ui::RpWidget>(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<Ui::FadeWrap<Ui::IconButton>>(
|
|
container,
|
|
object_ptr<Ui::IconButton>(
|
|
container,
|
|
st::userpicBuilderEmojiColorMinus));
|
|
_wraps.push_back(minus);
|
|
minus->toggle(_colors->size() > 1, anim::type::instant);
|
|
minus->entity()->setClickedCallback([=] {
|
|
if (_colors->size() < 2) {
|
|
return;
|
|
}
|
|
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<Ui::FadeWrap<CircleButton>>(
|
|
container,
|
|
object_ptr<CircleButton>(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<Ui::FadeWrap<Ui::IconButton>>(
|
|
container,
|
|
object_ptr<Ui::IconButton>(
|
|
container,
|
|
st::userpicBuilderEmojiColorPlus));
|
|
_wraps.push_back(plus);
|
|
plus->toggle(_colors->size() < kMaxColors, anim::type::instant);
|
|
plus->entity()->setClickedCallback([=] {
|
|
if (_colors->size() >= kMaxColors) {
|
|
return;
|
|
}
|
|
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::ButtonState> ColorsLine::calculatePositionFor(
|
|
int count) {
|
|
// Minus - Color - Color - Color - Color - Plus.
|
|
auto result = std::vector<ButtonState>(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<QColor> wasColors,
|
|
const std::vector<QColor> 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::Chosen*> ColorsLine::chosenChanges() const {
|
|
return _chosenChanges.events();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
object_ptr<Ui::RpWidget> CreateGradientEditor(
|
|
not_null<Ui::RpWidget*> parent,
|
|
DocumentData *document,
|
|
std::vector<QColor> startColors,
|
|
BothWayCommunication<std::vector<QColor>> communication) {
|
|
auto container = object_ptr<Ui::VerticalLayout>(parent.get());
|
|
|
|
struct State {
|
|
std::vector<QColor> colors;
|
|
};
|
|
const auto preview = container->add(
|
|
object_ptr<Ui::CenterWrap<EmojiUserpic>>(
|
|
container,
|
|
object_ptr<EmojiUserpic>(
|
|
container,
|
|
Size(st::defaultUserpicButton.photoSize),
|
|
false)))->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>();
|
|
state->colors = std::move(startColors);
|
|
const auto buttonsContainer = container->add(object_ptr<ColorsLine>(
|
|
container,
|
|
&state->colors));
|
|
buttonsContainer->resize(0, st::userpicBuilderEmojiAccentColorSize);
|
|
|
|
Settings::AddSkip(container);
|
|
Settings::AddDivider(container);
|
|
Settings::AddSkip(container);
|
|
|
|
const auto editor = container->add(object_ptr<ColorEditor>(
|
|
container,
|
|
ColorEditor::Mode::HSL,
|
|
state->colors.back()));
|
|
|
|
buttonsContainer->chosenChanges(
|
|
) | rpl::start_with_next([=](ColorsLine::Chosen *chosen) {
|
|
if (chosen) {
|
|
const auto color = state->colors[chosen->index()];
|
|
editor->showColor(color);
|
|
editor->setCurrent(color);
|
|
}
|
|
}, 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
|
|
|