mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-19 07:37:11 +02:00
Added initial implementation of gradient color editor.
This commit is contained in:
parent
16f59eee5d
commit
a61c4f1813
5 changed files with 361 additions and 1 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
309
Telegram/SourceFiles/info/userpic/info_userpic_colors_editor.cpp
Normal file
309
Telegram/SourceFiles/info/userpic/info_userpic_colors_editor.cpp
Normal file
|
@ -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<Ui::RpWidget*> parent,
|
||||
not_null<std::vector<QColor>*> colors);
|
||||
|
||||
void init();
|
||||
void fillButtons();
|
||||
Success addColor();
|
||||
|
||||
[[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();
|
||||
}
|
||||
|
||||
ColorsLine::Success ColorsLine::addColor() {
|
||||
}
|
||||
|
||||
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->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<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->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::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))))->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) {
|
||||
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
|
||||
|
|
@ -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 <typename Object>
|
||||
class object_ptr;
|
||||
|
||||
class DocumentData;
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace UserpicBuilder {
|
||||
|
||||
template <typename Result>
|
||||
struct BothWayCommunication;
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> CreateGradientEditor(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
DocumentData *document,
|
||||
std::vector<QColor> startColors,
|
||||
BothWayCommunication<std::vector<QColor>> communication);
|
||||
|
||||
} // namespace UserpicBuilder
|
|
@ -327,7 +327,7 @@ not_null<Ui::VerticalLayout*> CreateUserpicBuilder(
|
|||
1. - progress);
|
||||
}
|
||||
state->circleButtons[now]->setSelectedProgress(progress);
|
||||
}, 0., 1., st::slideDuration);
|
||||
}, 0., 1., st::userpicBuilderEmojiSlideDuration);
|
||||
state->colorIndex = now;
|
||||
|
||||
preview->setGradientColors(colors);
|
||||
|
|
Loading…
Add table
Reference in a new issue