init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
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_bubble_wrap.h"
|
||||
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_info_userpic_builder.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
void PaintExcludeTopShadow(QPainter &p, int radius, const QRect &r) {
|
||||
constexpr auto kHorizontalOffset = 1.;
|
||||
constexpr auto kVerticalOffset = 2.;
|
||||
constexpr auto kOpacityStep1 = 0.2;
|
||||
constexpr auto kOpacityStep2 = 0.4;
|
||||
const auto opacity = p.opacity();
|
||||
const auto hOffset = style::ConvertScale(kHorizontalOffset);
|
||||
const auto vOffset = style::ConvertScale(kVerticalOffset);
|
||||
p.setOpacity(opacity * kOpacityStep1);
|
||||
p.drawRoundedRect(
|
||||
r + QMarginsF(hOffset, -radius, hOffset, 0),
|
||||
radius,
|
||||
radius);
|
||||
p.setOpacity(opacity * kOpacityStep1);
|
||||
p.drawRoundedRect(
|
||||
r + QMarginsF(0, 0, 0, vOffset),
|
||||
radius,
|
||||
radius);
|
||||
p.setOpacity(opacity * kOpacityStep2);
|
||||
p.drawRoundedRect(
|
||||
r + QMarginsF(0, 0, 0, vOffset / 2.),
|
||||
radius,
|
||||
radius);
|
||||
p.setOpacity(opacity);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QRect BubbleWrapInnerRect(const QRect &r) {
|
||||
return r - st::userpicBuilderEmojiBubblePadding;
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> AddBubbleWrap(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const QSize &size) {
|
||||
const auto bubble = container->add(
|
||||
object_ptr<Ui::RpWidget>(container),
|
||||
style::al_top);
|
||||
bubble->resize(size);
|
||||
bubble->setNaturalWidth(size.width());
|
||||
|
||||
auto cached = QImage(
|
||||
size * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
cached.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
cached.fill(Qt::transparent);
|
||||
{
|
||||
auto p = QPainter(&cached);
|
||||
const auto innerRect = BubbleWrapInnerRect(bubble->rect());
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto radius = st::bubbleRadiusSmall;
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::shadowFg);
|
||||
PaintExcludeTopShadow(p, radius, innerRect);
|
||||
p.setBrush(st::boxBg);
|
||||
p.drawRoundedRect(innerRect, radius, radius);
|
||||
}
|
||||
|
||||
bubble->paintRequest(
|
||||
) | rpl::on_next([bubble, cached = std::move(cached)] {
|
||||
auto p = QPainter(bubble);
|
||||
p.drawImage(0, 0, cached);
|
||||
}, bubble->lifetime());
|
||||
|
||||
return bubble;
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
22
Telegram/SourceFiles/info/userpic/info_userpic_bubble_wrap.h
Normal file
22
Telegram/SourceFiles/info/userpic/info_userpic_bubble_wrap.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class VerticalLayout;
|
||||
|
||||
[[nodiscard]] QRect BubbleWrapInnerRect(const QRect &r);
|
||||
|
||||
not_null<Ui::RpWidget*> AddBubbleWrap(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const QSize &size);
|
||||
|
||||
} // namespace Ui
|
||||
68
Telegram/SourceFiles/info/userpic/info_userpic_builder.style
Normal file
68
Telegram/SourceFiles/info/userpic/info_userpic_builder.style
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
using "ui/basic.style";
|
||||
|
||||
using "boxes/boxes.style";
|
||||
using "ui/widgets/widgets.style";
|
||||
|
||||
userpicBuilderEmojiPreviewPadding: margins(0px, 36px, 0px, 8px);
|
||||
userpicBuilderEmojiSubtitle: FlatLabel(defaultFlatLabel) {
|
||||
align: align(center);
|
||||
minWidth: 356px;
|
||||
maxHeight: 24px;
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
userpicBuilderEmojiSubtitlePadding: margins(0px, 9px, 0px, 2px);
|
||||
userpicBuilderEmojiBubblePaletteWidth: 356px;
|
||||
userpicBuilderEmojiBubblePalettePadding: margins(12px, 8px, 12px, 8px);
|
||||
|
||||
userpicBuilderEmojiSelectorLeft: 5px;
|
||||
|
||||
userpicBuilderEmojiButton: RoundButton(defaultBoxButton) {
|
||||
textFg: boxTextFg;
|
||||
textFgOver: boxTextFg;
|
||||
textBg: boxDividerBg;
|
||||
textBgOver: boxDividerBg;
|
||||
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
userpicBuilderEmojiBackButton: IconButton(backButton) {
|
||||
icon: icon {{ "box_button_back", boxTextFg }};
|
||||
iconOver: icon {{ "box_button_back", boxTextFg }};
|
||||
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
|
||||
userpicBuilderEmojiBackPosiiton: point(8px, 8px);
|
||||
userpicBuilderEmojiSavePosiiton: point(7px, 12px);
|
||||
|
||||
userpicBuilderEmojiAccentColorSize: 30px;
|
||||
userpicBuilderEmojiBubblePadding: margins(5px, 5px, 5px, 5px);
|
||||
|
||||
userpicBuilderEmojiLayerMinHeight: 496px;
|
||||
userpicBuilderEmojiSelectorMinHeight: 216px;
|
||||
userpicBuilderEmojiSelectorTogglePosition: point(6px, 6px);
|
||||
|
||||
userpicBuilderEmojiColorMinus: IconButton(defaultIconButton) {
|
||||
width: userpicBuilderEmojiAccentColorSize;
|
||||
height: userpicBuilderEmojiAccentColorSize;
|
||||
|
||||
icon: icon {{ "info/info_colors_remove", menuIconFg }};
|
||||
iconOver: icon {{ "info/info_colors_remove", menuIconFg }};
|
||||
|
||||
rippleAreaSize: userpicBuilderEmojiAccentColorSize;
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
|
||||
userpicBuilderEmojiColorPlus: IconButton(userpicBuilderEmojiColorMinus) {
|
||||
icon: icon {{ "info/info_colors_add", menuIconFg }};
|
||||
iconOver: icon {{ "info/info_colors_add", menuIconFg }};
|
||||
}
|
||||
|
||||
userpicBuilderEmojiToggleStickersIcon: icon {{ "menu/stickers", emojiIconFg }};
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
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_color_circle_button.h"
|
||||
|
||||
#include "settings/settings_chat.h" // Settings::PaintRoundColorButton.
|
||||
#include "ui/painter.h"
|
||||
|
||||
namespace UserpicBuilder {
|
||||
|
||||
void CircleButton::setIndex(int index) {
|
||||
_index = index;
|
||||
}
|
||||
|
||||
int CircleButton::index() const {
|
||||
return _index;
|
||||
}
|
||||
|
||||
void CircleButton::setBrush(QBrush brush) {
|
||||
_brush = brush;
|
||||
update();
|
||||
}
|
||||
|
||||
void CircleButton::setSelectedProgress(float64 progress) {
|
||||
if (_selectedProgress != progress) {
|
||||
_selectedProgress = progress;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void CircleButton::paintEvent(QPaintEvent *event) {
|
||||
auto p = QPainter(this);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto h = height();
|
||||
Settings::PaintRoundColorButton(p, h, _brush, _selectedProgress);
|
||||
}
|
||||
|
||||
} // namespace UserpicBuilder
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "ui/abstract_button.h"
|
||||
|
||||
namespace UserpicBuilder {
|
||||
|
||||
class CircleButton final : public Ui::AbstractButton {
|
||||
public:
|
||||
using Ui::AbstractButton::AbstractButton;
|
||||
|
||||
void setIndex(int index);
|
||||
[[nodiscard]] int index() const;
|
||||
void setBrush(QBrush brush);
|
||||
void setSelectedProgress(float64 progress);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
int _index = 0;
|
||||
float64 _selectedProgress = 0.;
|
||||
QBrush _brush;
|
||||
|
||||
};
|
||||
|
||||
} // namespace UserpicBuilder
|
||||
316
Telegram/SourceFiles/info/userpic/info_userpic_colors_editor.cpp
Normal file
316
Telegram/SourceFiles/info/userpic/info_userpic_colors_editor.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
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 "ui/widgets/buttons.h"
|
||||
#include "ui/vertical_list.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::on_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::universalDuration);
|
||||
});
|
||||
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::universalDuration);
|
||||
}
|
||||
}
|
||||
|
||||
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::universalDuration);
|
||||
}
|
||||
|
||||
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<EmojiUserpic>(
|
||||
container,
|
||||
Size(st::defaultUserpicButton.photoSize),
|
||||
false),
|
||||
style::al_top);
|
||||
preview->setDuration(0);
|
||||
if (document) {
|
||||
preview->setDocument(document);
|
||||
}
|
||||
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddDivider(container);
|
||||
Ui::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);
|
||||
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddDivider(container);
|
||||
Ui::AddSkip(container);
|
||||
|
||||
auto ownedEditor = object_ptr<ColorEditor>(
|
||||
container,
|
||||
ColorEditor::Mode::HSL,
|
||||
state->colors.back());
|
||||
container->resizeToWidth(ownedEditor->width());
|
||||
const auto editor = container->add(std::move(ownedEditor));
|
||||
|
||||
buttonsContainer->chosenChanges(
|
||||
) | rpl::on_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::on_next([=] {
|
||||
// }, editor->lifetime());
|
||||
editor->colorValue(
|
||||
) | rpl::on_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::on_next([=] {
|
||||
save();
|
||||
}, container->lifetime());
|
||||
|
||||
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
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
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_emoji_builder.h"
|
||||
|
||||
#include "info/userpic/info_userpic_emoji_builder_common.h"
|
||||
#include "info/userpic/info_userpic_emoji_builder_layer.h"
|
||||
#include "info/userpic/info_userpic_emoji_builder_widget.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_info_userpic_builder.h"
|
||||
|
||||
namespace UserpicBuilder {
|
||||
|
||||
void ShowLayer(
|
||||
not_null<Window::SessionController*> controller,
|
||||
StartData data,
|
||||
Fn<void(UserpicBuilder::Result)> &&doneCallback) {
|
||||
auto layer = std::make_unique<LayerWidget>();
|
||||
const auto layerRaw = layer.get();
|
||||
{
|
||||
struct State {
|
||||
rpl::event_stream<> clicks;
|
||||
};
|
||||
const auto state = layer->lifetime().make_state<State>();
|
||||
|
||||
const auto content = CreateUserpicBuilder(
|
||||
layerRaw,
|
||||
controller,
|
||||
data,
|
||||
BothWayCommunication<UserpicBuilder::Result>{
|
||||
.triggers = state->clicks.events(),
|
||||
.result = [=, done = std::move(doneCallback)](Result r) {
|
||||
done(std::move(r));
|
||||
layerRaw->closeLayer();
|
||||
},
|
||||
});
|
||||
const auto save = Ui::CreateChild<Ui::RoundButton>(
|
||||
content.get(),
|
||||
tr::lng_connection_save(),
|
||||
st::userpicBuilderEmojiButton);
|
||||
save->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
content->sizeValue(
|
||||
) | rpl::on_next([=] {
|
||||
const auto &p = st::userpicBuilderEmojiSavePosiiton;
|
||||
save->moveToRight(p.x(), p.y());
|
||||
}, save->lifetime());
|
||||
|
||||
save->clicks() | rpl::to_empty | rpl::start_to_stream(
|
||||
state->clicks,
|
||||
save->lifetime());
|
||||
|
||||
const auto back = Ui::CreateChild<Ui::IconButton>(
|
||||
content.get(),
|
||||
st::userpicBuilderEmojiBackButton);
|
||||
back->setClickedCallback([=] {
|
||||
layerRaw->closeLayer();
|
||||
});
|
||||
content->sizeValue(
|
||||
) | rpl::on_next([=] {
|
||||
const auto &p = st::userpicBuilderEmojiBackPosiiton;
|
||||
back->moveToLeft(p.x(), p.y());
|
||||
}, back->lifetime());
|
||||
|
||||
layer->setContent(content);
|
||||
}
|
||||
|
||||
controller->showLayer(std::move(layer), Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
} // namespace UserpicBuilder
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
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
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace UserpicBuilder {
|
||||
|
||||
struct Result;
|
||||
struct StartData;
|
||||
|
||||
void ShowLayer(
|
||||
not_null<Window::SessionController*> controller,
|
||||
StartData data,
|
||||
Fn<void(UserpicBuilder::Result)> &&doneCallback);
|
||||
|
||||
} // namespace UserpicBuilder
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
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_emoji_builder_common.h"
|
||||
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/userpic_view.h" // ForumUserpicRadiusMultiplier.
|
||||
|
||||
namespace UserpicBuilder {
|
||||
|
||||
[[nodiscard]] QImage GenerateGradient(
|
||||
const QSize &size,
|
||||
const std::vector<QColor> &colors,
|
||||
bool circle,
|
||||
bool roundForumRect) {
|
||||
constexpr auto kRotation = int(45);
|
||||
auto gradient = Images::GenerateGradient(size, colors, kRotation);
|
||||
if (!circle && !roundForumRect) {
|
||||
return gradient;
|
||||
}
|
||||
const auto processModifier = [&](QImage &&i) {
|
||||
if (circle) {
|
||||
return Images::Circle(std::move(i));
|
||||
} else if (roundForumRect) {
|
||||
const auto radius = std::min(i.height(), i.width())
|
||||
* Ui::ForumUserpicRadiusMultiplier();
|
||||
return Images::Round(
|
||||
std::move(i),
|
||||
Images::CornersMask(radius / style::DevicePixelRatio()));
|
||||
} else {
|
||||
return std::move(i);
|
||||
}
|
||||
};
|
||||
if (style::DevicePixelRatio() == 1) {
|
||||
return processModifier(std::move(gradient));
|
||||
}
|
||||
auto image = QImage(
|
||||
size * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
image.fill(Qt::transparent);
|
||||
{
|
||||
auto p = QPainter(&image);
|
||||
p.drawImage(QRect(QPoint(), size), gradient);
|
||||
}
|
||||
return processModifier(std::move(image));
|
||||
}
|
||||
|
||||
} // namespace UserpicBuilder
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
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
|
||||
|
||||
namespace UserpicBuilder {
|
||||
|
||||
struct Result {
|
||||
QImage&& image;
|
||||
DocumentId id = 0;
|
||||
std::vector<QColor> colors;
|
||||
};
|
||||
|
||||
[[nodiscard]] QImage GenerateGradient(
|
||||
const QSize &size,
|
||||
const std::vector<QColor> &colors,
|
||||
bool circle = true,
|
||||
bool roundForumRect = false);
|
||||
|
||||
struct StartData {
|
||||
DocumentId documentId = DocumentId(0);
|
||||
int builderColorIndex = 0;
|
||||
rpl::producer<std::vector<DocumentId>> documents;
|
||||
std::vector<QColor> gradientEditorColors;
|
||||
bool isForum = false;
|
||||
};
|
||||
|
||||
template <typename Result>
|
||||
struct BothWayCommunication {
|
||||
rpl::producer<> triggers;
|
||||
Fn<void(Result)> result;
|
||||
};
|
||||
|
||||
} // namespace UserpicBuilder
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
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_emoji_builder_layer.h"
|
||||
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_info_userpic_builder.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
namespace UserpicBuilder {
|
||||
|
||||
LayerWidget::LayerWidget()
|
||||
: _corners(Ui::PrepareCornerPixmaps(st::boxRadius, st::boxDividerBg)) {
|
||||
}
|
||||
|
||||
void LayerWidget::setContent(not_null<Ui::RpWidget*> content) {
|
||||
_content = content;
|
||||
}
|
||||
|
||||
void LayerWidget::parentResized() {
|
||||
Expects(_content != nullptr);
|
||||
const auto parentSize = parentWidget()->size();
|
||||
const auto currentHeight = resizeGetHeight(0);
|
||||
const auto currentWidth = _content->width();
|
||||
resizeToWidth(currentWidth);
|
||||
moveToLeft(
|
||||
(parentSize.width() - currentWidth) / 2,
|
||||
(parentSize.height() - currentHeight) / 2);
|
||||
}
|
||||
|
||||
bool LayerWidget::closeByOutsideClick() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
int LayerWidget::resizeGetHeight(int newWidth) {
|
||||
Expects(_content != nullptr);
|
||||
_content->resizeToWidth(st::infoDesiredWidth);
|
||||
return st::userpicBuilderEmojiLayerMinHeight;
|
||||
}
|
||||
|
||||
void LayerWidget::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
|
||||
Ui::FillRoundRect(p, rect(), st::boxDividerBg, _corners);
|
||||
}
|
||||
|
||||
} // namespace UserpicBuilder
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "ui/layers/layer_widget.h"
|
||||
|
||||
#include "ui/cached_round_corners.h"
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace UserpicBuilder {
|
||||
|
||||
class LayerWidget : public Ui::LayerWidget {
|
||||
public:
|
||||
LayerWidget();
|
||||
|
||||
void setContent(not_null<Ui::RpWidget*> content);
|
||||
|
||||
void parentResized() override;
|
||||
bool closeByOutsideClick() const override;
|
||||
|
||||
protected:
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
const Ui::CornersPixmaps _corners;
|
||||
Ui::RpWidget *_content;
|
||||
|
||||
};
|
||||
|
||||
} // namespace UserpicBuilder
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
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_emoji_builder_menu_item.h"
|
||||
|
||||
#include "base/random.h"
|
||||
#include "base/timer.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "info/userpic/info_userpic_emoji_builder.h"
|
||||
#include "info/userpic/info_userpic_emoji_builder_common.h"
|
||||
#include "info/userpic/info_userpic_emoji_builder_widget.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/widgets/menu/menu_action.h"
|
||||
#include "ui/widgets/menu/menu_common.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace UserpicBuilder {
|
||||
namespace {
|
||||
|
||||
constexpr auto kTimeout = crl::time(1500);
|
||||
|
||||
class StickerProvider final {
|
||||
public:
|
||||
StickerProvider(not_null<Data::Session*> owner);
|
||||
|
||||
void setDocuments(std::vector<DocumentId> documents);
|
||||
[[nodiscard]] DocumentId documentId() const;
|
||||
[[nodiscard]] auto documentChanged() const
|
||||
-> rpl::producer<not_null<DocumentData*>>;
|
||||
|
||||
private:
|
||||
void processDocumentIndex(int documentIndex);
|
||||
[[nodiscard]] DocumentData *lookupAndRememberSticker(int documentIndex);
|
||||
[[nodiscard]] std::pair<DocumentData*, int> lookupSticker(
|
||||
int documentIndex) const;
|
||||
|
||||
const not_null<Data::Session*> _owner;
|
||||
|
||||
int _documentIndex = 0;
|
||||
std::vector<DocumentId> _shuffledDocuments;
|
||||
|
||||
base::Timer _timer;
|
||||
|
||||
rpl::event_stream<not_null<DocumentData*>> _documentChanged;
|
||||
rpl::lifetime _resolvingLifetime;
|
||||
rpl::lifetime _downloadFinishedLifetime;
|
||||
|
||||
};
|
||||
|
||||
StickerProvider::StickerProvider(not_null<Data::Session*> owner)
|
||||
: _owner(owner) {
|
||||
_timer.setCallback([=] {
|
||||
_documentIndex++;
|
||||
if (_documentIndex >= _shuffledDocuments.size()) {
|
||||
_documentIndex = 0;
|
||||
}
|
||||
processDocumentIndex(_documentIndex);
|
||||
});
|
||||
}
|
||||
|
||||
DocumentId StickerProvider::documentId() const {
|
||||
const auto &[document, index] = lookupSticker(_documentIndex);
|
||||
return document ? document->id : DocumentId(0);
|
||||
}
|
||||
|
||||
void StickerProvider::setDocuments(std::vector<DocumentId> documents) {
|
||||
if (documents.empty()) {
|
||||
return;
|
||||
}
|
||||
auto rd = std::random_device();
|
||||
ranges::shuffle(documents, std::mt19937(rd()));
|
||||
_shuffledDocuments = std::move(documents);
|
||||
_documentIndex = 0;
|
||||
processDocumentIndex(_documentIndex);
|
||||
}
|
||||
|
||||
auto StickerProvider::documentChanged() const
|
||||
-> rpl::producer<not_null<DocumentData*>> {
|
||||
return _documentChanged.events();
|
||||
}
|
||||
|
||||
void StickerProvider::processDocumentIndex(int documentIndex) {
|
||||
if (const auto document = lookupAndRememberSticker(documentIndex)) {
|
||||
_resolvingLifetime.destroy();
|
||||
_owner->customEmojiManager().resolve(
|
||||
document->id
|
||||
) | rpl::on_next([=](not_null<DocumentData*> d) {
|
||||
_resolvingLifetime.destroy();
|
||||
_downloadFinishedLifetime.destroy();
|
||||
|
||||
const auto mediaView = d->createMediaView();
|
||||
_downloadFinishedLifetime.add([=] {
|
||||
[[maybe_unused]] const auto copy = mediaView;
|
||||
});
|
||||
mediaView->checkStickerLarge();
|
||||
mediaView->goodThumbnailWanted();
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
_owner->session().downloaderTaskFinished()
|
||||
) | rpl::on_next([=] {
|
||||
if (mediaView->loaded()) {
|
||||
_timer.callOnce(kTimeout);
|
||||
_documentChanged.fire_copy(mediaView->owner());
|
||||
_downloadFinishedLifetime.destroy();
|
||||
}
|
||||
}, _downloadFinishedLifetime);
|
||||
}, _resolvingLifetime);
|
||||
} else if (!_resolvingLifetime) {
|
||||
_timer.callOnce(kTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
DocumentData *StickerProvider::lookupAndRememberSticker(int documentIndex) {
|
||||
const auto &[document, index] = lookupSticker(documentIndex);
|
||||
if (document) {
|
||||
_documentIndex = index;
|
||||
}
|
||||
return document;
|
||||
}
|
||||
|
||||
std::pair<DocumentData*, int> StickerProvider::lookupSticker(
|
||||
int documentIndex) const {
|
||||
const auto size = _shuffledDocuments.size();
|
||||
for (auto i = 0; i < size; i++) {
|
||||
const auto unrestrictedIndex = documentIndex + i;
|
||||
const auto index = (unrestrictedIndex >= size)
|
||||
? (unrestrictedIndex - size)
|
||||
: unrestrictedIndex;
|
||||
const auto id = _shuffledDocuments[index];
|
||||
const auto document = _owner->document(id);
|
||||
if (document->sticker()) {
|
||||
return { document, index };
|
||||
}
|
||||
}
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void AddEmojiBuilderAction(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::PopupMenu*> menu,
|
||||
rpl::producer<std::vector<DocumentId>> documents,
|
||||
Fn<void(UserpicBuilder::Result)> &&done,
|
||||
bool isForum) {
|
||||
|
||||
struct State final {
|
||||
State(not_null<Window::SessionController*> controller)
|
||||
: manager(&controller->session().data())
|
||||
, colorIndex(rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
manager.documentChanged() | rpl::skip(1) | rpl::to_empty
|
||||
) | rpl::map([] {
|
||||
return base::RandomIndex(std::numeric_limits<int>::max());
|
||||
})) {
|
||||
}
|
||||
|
||||
StickerProvider manager;
|
||||
rpl::variable<int> colorIndex;
|
||||
};
|
||||
const auto state = menu->lifetime().make_state<State>(controller);
|
||||
auto item = base::make_unique_q<Ui::Menu::Action>(
|
||||
menu.get(),
|
||||
menu->st().menu,
|
||||
Ui::Menu::CreateAction(
|
||||
menu.get(),
|
||||
tr::lng_attach_profile_emoji(tr::now),
|
||||
[=, done = std::move(done), docs = rpl::duplicate(documents)] {
|
||||
const auto id = state->manager.documentId();
|
||||
UserpicBuilder::ShowLayer(
|
||||
controller,
|
||||
{ id, state->colorIndex.current(), docs, {}, isForum },
|
||||
base::duplicate(done));
|
||||
}),
|
||||
nullptr,
|
||||
nullptr);
|
||||
const auto icon = UserpicBuilder::CreateEmojiUserpic(
|
||||
item.get(),
|
||||
st::restoreUserpicIcon.size,
|
||||
state->manager.documentChanged(),
|
||||
state->colorIndex.value(),
|
||||
isForum);
|
||||
icon->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
icon->move(menu->st().menu.itemIconPosition
|
||||
+ QPoint(
|
||||
(st::menuIconRemove.width() - icon->width()) / 2,
|
||||
(st::menuIconRemove.height() - icon->height()) / 2));
|
||||
|
||||
rpl::duplicate(
|
||||
documents
|
||||
) | rpl::on_next([=](std::vector<DocumentId> documents) {
|
||||
state->manager.setDocuments(std::move(documents));
|
||||
}, item->lifetime());
|
||||
|
||||
menu->addAction(std::move(item));
|
||||
}
|
||||
|
||||
} // namespace UserpicBuilder
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
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
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
class PopupMenu;
|
||||
} // namespace Ui
|
||||
|
||||
namespace UserpicBuilder {
|
||||
|
||||
struct Result;
|
||||
|
||||
void AddEmojiBuilderAction(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::PopupMenu*> menu,
|
||||
rpl::producer<std::vector<DocumentId>> documents,
|
||||
Fn<void(UserpicBuilder::Result)> &&done,
|
||||
bool isForum);
|
||||
|
||||
} // namespace UserpicBuilder
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
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_emoji_builder_preview.h"
|
||||
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/view/media/history_view_sticker_player.h"
|
||||
#include "info/userpic/info_userpic_emoji_builder_common.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
|
||||
namespace UserpicBuilder {
|
||||
|
||||
PreviewPainter::PreviewPainter(int size)
|
||||
: _size(size)
|
||||
, _emojiSize(base::SafeRound(_size / M_SQRT2))
|
||||
, _frameGeometry(Rect(Size(_size)) - Margins((_size - _emojiSize) / 2))
|
||||
, _frameRect(Rect(_frameGeometry.size()))
|
||||
, _mask(
|
||||
_frameRect.size() * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied)
|
||||
, _frame(_mask.size(), QImage::Format_ARGB32_Premultiplied) {
|
||||
_frame.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
_mask.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
_mask.fill(Qt::transparent);
|
||||
{
|
||||
auto p = QPainter(&_mask);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::windowBg);
|
||||
constexpr auto kFrameRadiusPercent = 25;
|
||||
p.drawRoundedRect(
|
||||
_frameRect,
|
||||
kFrameRadiusPercent,
|
||||
kFrameRadiusPercent,
|
||||
Qt::RelativeSize);
|
||||
}
|
||||
}
|
||||
|
||||
DocumentData *PreviewPainter::document() const {
|
||||
return _media ? _media->owner().get() : nullptr;
|
||||
}
|
||||
|
||||
void PreviewPainter::setPlayOnce(bool value) {
|
||||
_playOnce = value;
|
||||
}
|
||||
|
||||
void PreviewPainter::setDocument(
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void()> updateCallback) {
|
||||
if (_media && (document == _media->owner())) {
|
||||
return;
|
||||
}
|
||||
_lifetime.destroy();
|
||||
|
||||
const auto sticker = document->sticker();
|
||||
Assert(sticker != nullptr);
|
||||
_media = document->createMediaView();
|
||||
_media->checkStickerLarge();
|
||||
_media->goodThumbnailWanted();
|
||||
|
||||
if (_playOnce) {
|
||||
_firstFrameShown = false;
|
||||
_paused = false;
|
||||
} else {
|
||||
_paused = true;
|
||||
}
|
||||
|
||||
rpl::single() | rpl::then(
|
||||
document->session().downloaderTaskFinished()
|
||||
) | rpl::on_next([=] {
|
||||
if (!_media->loaded()) {
|
||||
return;
|
||||
}
|
||||
_lifetime.destroy();
|
||||
const auto emojiSize = Size(_size * style::DevicePixelRatio());
|
||||
if (sticker->isLottie()) {
|
||||
_player = std::make_unique<HistoryView::LottiePlayer>(
|
||||
ChatHelpers::LottiePlayerFromDocument(
|
||||
_media.get(),
|
||||
//
|
||||
ChatHelpers::StickerLottieSize::EmojiInteractionReserved7,
|
||||
emojiSize,
|
||||
Lottie::Quality::High));
|
||||
} else if (sticker->isWebm()) {
|
||||
_player = std::make_unique<HistoryView::WebmPlayer>(
|
||||
_media->owner()->location(),
|
||||
_media->bytes(),
|
||||
emojiSize);
|
||||
} else if (sticker) {
|
||||
_player = std::make_unique<HistoryView::StaticStickerPlayer>(
|
||||
_media->owner()->location(),
|
||||
_media->bytes(),
|
||||
emojiSize);
|
||||
}
|
||||
if (_player) {
|
||||
_player->setRepaintCallback(updateCallback);
|
||||
} else if (updateCallback) {
|
||||
updateCallback();
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void PreviewPainter::paintBackground(QPainter &p, const QImage &image) {
|
||||
p.drawImage(0, 0, image);
|
||||
}
|
||||
|
||||
bool PreviewPainter::paintForeground(QPainter &p) {
|
||||
if (_player && _player->ready()) {
|
||||
const auto c = _media->owner()->emojiUsesTextColor() ? 255 : 0;
|
||||
auto frame = _player->frame(
|
||||
Size(_emojiSize),
|
||||
QColor(c, c, c, c),
|
||||
false,
|
||||
crl::now(),
|
||||
_paused);
|
||||
|
||||
if (_playOnce) {
|
||||
if (!_firstFrameShown && (frame.index == 1)) {
|
||||
_firstFrameShown = true;
|
||||
} else if (_firstFrameShown && !frame.index) {
|
||||
_paused = true;
|
||||
}
|
||||
}
|
||||
|
||||
_frame.fill(Qt::transparent);
|
||||
{
|
||||
QPainter q(&_frame);
|
||||
if (frame.image.width() == frame.image.height()) {
|
||||
q.drawImage(_frameRect, frame.image);
|
||||
} else {
|
||||
auto frameRect = Rect(frame.image.size().scaled(
|
||||
_frameRect.size(),
|
||||
Qt::KeepAspectRatio));
|
||||
frameRect.moveCenter(_frameRect.center());
|
||||
q.drawImage(frameRect, frame.image);
|
||||
}
|
||||
q.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
||||
q.drawImage(0, 0, _mask);
|
||||
}
|
||||
|
||||
p.drawImage(_frameGeometry.topLeft(), _frame);
|
||||
if (!_paused) {
|
||||
_player->markFrameShown();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
EmojiUserpic::EmojiUserpic(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const QSize &size,
|
||||
bool isForum)
|
||||
: Ui::RpWidget(parent)
|
||||
, _forum(isForum)
|
||||
, _painter(size.width())
|
||||
, _duration(st::slideWrapDuration) {
|
||||
resize(size);
|
||||
setNaturalWidth(size.width());
|
||||
}
|
||||
|
||||
void EmojiUserpic::setDocument(not_null<DocumentData*> document) {
|
||||
if (!_playOnce.has_value()) {
|
||||
const auto &c = document->session().appConfig();
|
||||
_playOnce = !c.get<bool>(u"upload_markup_video"_q, false);
|
||||
}
|
||||
_painter.setDocument(document, [=] { update(); });
|
||||
_painter.setPlayOnce(*_playOnce);
|
||||
}
|
||||
|
||||
void EmojiUserpic::result(int size, Fn<void(UserpicBuilder::Result)> done) {
|
||||
const auto painter = lifetime().make_state<PreviewPainter>(size);
|
||||
// Reset to the first frame.
|
||||
const auto document = _painter.document();
|
||||
const auto callback = [=] {
|
||||
auto background = GenerateGradient(Size(size), _colors, false);
|
||||
|
||||
{
|
||||
constexpr auto kAttemptsToDrawFirstFrame = 3000;
|
||||
auto attempts = 0;
|
||||
auto p = QPainter(&background);
|
||||
while (attempts < kAttemptsToDrawFirstFrame) {
|
||||
if (painter->paintForeground(p)) {
|
||||
break;
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
}
|
||||
if (*_playOnce && document) {
|
||||
done({ std::move(background), document->id, _colors });
|
||||
} else {
|
||||
done({ std::move(background) });
|
||||
}
|
||||
};
|
||||
if (document) {
|
||||
painter->setDocument(document, callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
void EmojiUserpic::setGradientColors(std::vector<QColor> colors) {
|
||||
if (_colors == colors) {
|
||||
return;
|
||||
}
|
||||
if (const auto colors = base::take(_colors); !colors.empty()) {
|
||||
_previousImage = GenerateGradient(size(), colors, !_forum, _forum);
|
||||
}
|
||||
_colors = std::move(colors);
|
||||
{
|
||||
_image = GenerateGradient(size(), _colors, !_forum, _forum);
|
||||
}
|
||||
if (_duration) {
|
||||
_animation.stop();
|
||||
_animation.start([=] { update(); }, 0., 1., _duration);
|
||||
} else {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void EmojiUserpic::paintEvent(QPaintEvent *event) {
|
||||
auto p = QPainter(this);
|
||||
|
||||
if (_animation.animating() && !_previousImage.isNull()) {
|
||||
_painter.paintBackground(p, _previousImage);
|
||||
|
||||
p.setOpacity(_animation.value(1.));
|
||||
}
|
||||
|
||||
_painter.paintBackground(p, _image);
|
||||
|
||||
p.setOpacity(1.);
|
||||
_painter.paintForeground(p);
|
||||
}
|
||||
|
||||
void EmojiUserpic::setDuration(crl::time duration) {
|
||||
_duration = duration;
|
||||
}
|
||||
|
||||
} // namespace UserpicBuilder
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
namespace Data {
|
||||
class DocumentMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace HistoryView {
|
||||
class StickerPlayer;
|
||||
} // namespace HistoryView
|
||||
|
||||
class DocumentData;
|
||||
|
||||
namespace UserpicBuilder {
|
||||
|
||||
struct Result;
|
||||
|
||||
class PreviewPainter final {
|
||||
public:
|
||||
PreviewPainter(int size);
|
||||
|
||||
[[nodiscard]] DocumentData *document() const;
|
||||
|
||||
void setPlayOnce(bool value);
|
||||
void setDocument(
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void()> updateCallback);
|
||||
|
||||
void paintBackground(QPainter &p, const QImage &image);
|
||||
bool paintForeground(QPainter &p);
|
||||
|
||||
private:
|
||||
const int _size;
|
||||
const int _emojiSize;
|
||||
const QRect _frameGeometry;
|
||||
const QRect _frameRect;
|
||||
|
||||
QImage _mask;
|
||||
QImage _frame;
|
||||
|
||||
std::shared_ptr<Data::DocumentMedia> _media;
|
||||
std::unique_ptr<HistoryView::StickerPlayer> _player;
|
||||
bool _playOnce = false;
|
||||
bool _paused = false;
|
||||
bool _firstFrameShown = false;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
class EmojiUserpic final : public Ui::RpWidget {
|
||||
public:
|
||||
EmojiUserpic(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const QSize &size,
|
||||
bool isForum);
|
||||
|
||||
void result(int size, Fn<void(UserpicBuilder::Result)> done);
|
||||
void setGradientColors(std::vector<QColor> colors);
|
||||
void setDocument(not_null<DocumentData*> document);
|
||||
void setDuration(crl::time duration);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
const bool _forum;
|
||||
PreviewPainter _painter;
|
||||
|
||||
std::optional<bool> _playOnce;
|
||||
|
||||
QImage _previousImage;
|
||||
QImage _image;
|
||||
std::vector<QColor> _colors;
|
||||
|
||||
crl::time _duration;
|
||||
Ui::Animations::Simple _animation;
|
||||
|
||||
};
|
||||
|
||||
} // namespace UserpicBuilder
|
||||
@@ -0,0 +1,578 @@
|
||||
/*
|
||||
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_emoji_builder_widget.h"
|
||||
|
||||
#include "api/api_peer_photo.h"
|
||||
#include "apiwrap.h"
|
||||
#include "chat_helpers/emoji_list_widget.h"
|
||||
#include "chat_helpers/stickers_list_widget.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "editor/photo_editor_layer_widget.h" // Editor::kProfilePhotoSize.
|
||||
#include "info/userpic/info_userpic_bubble_wrap.h"
|
||||
#include "info/userpic/info_userpic_color_circle_button.h"
|
||||
#include "info/userpic/info_userpic_colors_editor.h"
|
||||
#include "info/userpic/info_userpic_emoji_builder_common.h"
|
||||
#include "info/userpic/info_userpic_emoji_builder_preview.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/controls/emoji_button.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_info_userpic_builder.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
namespace UserpicBuilder {
|
||||
namespace {
|
||||
|
||||
void AlignChildren(not_null<Ui::RpWidget*> widget, int fullWidth) {
|
||||
const auto children = widget->children();
|
||||
const auto widgets = ranges::views::all(
|
||||
children
|
||||
) | ranges::views::filter([](not_null<const QObject*> object) {
|
||||
return object->isWidgetType();
|
||||
}) | ranges::views::transform([](not_null<QObject*> object) {
|
||||
return static_cast<QWidget*>(object.get());
|
||||
}) | ranges::to_vector;
|
||||
|
||||
const auto widgetWidth = widgets.front()->width();
|
||||
const auto widgetsCount = widgets.size();
|
||||
const auto widgetsWidth = widgetWidth * widgetsCount;
|
||||
const auto step = (fullWidth - widgetsWidth) / (widgetsCount - 1);
|
||||
for (auto i = 0; i < widgetsCount; i++) {
|
||||
widgets[i]->move(i * (widgetWidth + step), widgets[i]->y());
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage GenerateSpecial(
|
||||
int size,
|
||||
const std::vector<QColor> colors) {
|
||||
if (colors.empty()) {
|
||||
auto image = QImage(
|
||||
Size(size * style::DevicePixelRatio()),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
image.fill(Qt::transparent);
|
||||
{
|
||||
auto p = QPainter(&image);
|
||||
st::userpicBuilderEmojiColorPlus.icon.paintInCenter(
|
||||
p,
|
||||
Rect(Size(size)));
|
||||
}
|
||||
return image;
|
||||
} else {
|
||||
auto image = GenerateGradient(Size(size), colors);
|
||||
{
|
||||
auto p = QPainter(&image);
|
||||
constexpr auto kEllipseSize = 1;
|
||||
const auto center = QPointF(size / 2., size / 2.);
|
||||
const auto shift = QPointF(kEllipseSize * 4, 0);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::boxBg);
|
||||
p.drawEllipse(center, kEllipseSize, kEllipseSize);
|
||||
p.drawEllipse(center + shift, kEllipseSize, kEllipseSize);
|
||||
p.drawEllipse(center - shift, kEllipseSize, kEllipseSize);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<std::vector<QColor>> PaletteGradients() {
|
||||
auto v = std::vector<std::vector<QColor>>{
|
||||
{
|
||||
QColor(32, 226, 205),
|
||||
QColor(14, 225, 241),
|
||||
QColor(77, 141, 255),
|
||||
QColor(43, 191, 255),
|
||||
},
|
||||
{
|
||||
QColor(69, 247, 183),
|
||||
QColor(31, 241, 217),
|
||||
QColor(94, 182, 251),
|
||||
QColor(31, 206, 235),
|
||||
},
|
||||
{
|
||||
QColor(193, 229, 38),
|
||||
QColor(128, 223, 43),
|
||||
QColor(9, 210, 96),
|
||||
QColor(94, 220, 64),
|
||||
},
|
||||
{
|
||||
QColor(255, 212, 18),
|
||||
QColor(255, 167, 67),
|
||||
QColor(245, 105, 78),
|
||||
QColor(245, 119, 44),
|
||||
},
|
||||
{
|
||||
QColor(246, 167, 48),
|
||||
QColor(255, 119, 66),
|
||||
QColor(246, 72, 132),
|
||||
QColor(239, 91, 65),
|
||||
},
|
||||
{
|
||||
QColor(255, 178, 58),
|
||||
QColor(254, 126, 98),
|
||||
QColor(249, 75, 160),
|
||||
QColor(251, 92, 128),
|
||||
},
|
||||
{
|
||||
QColor(255, 114, 169),
|
||||
QColor(226, 105, 255),
|
||||
QColor(131, 124, 255),
|
||||
QColor(176, 99, 255),
|
||||
},
|
||||
};
|
||||
for (auto &g : v) {
|
||||
// Rotate 180 degrees.
|
||||
std::swap(g[0], g[2]);
|
||||
std::swap(g[1], g[3]);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
void ShowGradientEditor(
|
||||
not_null<Window::SessionController*> controller,
|
||||
StartData data,
|
||||
Fn<void(std::vector<QColor>)> &&doneCallback) {
|
||||
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
struct State {
|
||||
rpl::event_stream<> saveRequests;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
box->setTitle(tr::lng_chat_theme_change());
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
state->saveRequests.fire({});
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
|
||||
auto content = CreateGradientEditor(
|
||||
box,
|
||||
(data.documentId
|
||||
? controller->session().data().document(
|
||||
data.documentId).get()
|
||||
: nullptr),
|
||||
data.gradientEditorColors,
|
||||
BothWayCommunication<std::vector<QColor>>{
|
||||
state->saveRequests.events(),
|
||||
[=](std::vector<QColor> colors) {
|
||||
box->closeBox();
|
||||
doneCallback(std::move(colors));
|
||||
},
|
||||
});
|
||||
box->setWidth(content->width());
|
||||
box->addRow(std::move(content), style::margins());
|
||||
}));
|
||||
}
|
||||
|
||||
class EmojiSelector final : public Ui::RpWidget {
|
||||
public:
|
||||
EmojiSelector(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
rpl::producer<std::vector<DocumentId>> recent);
|
||||
|
||||
[[nodiscard]] rpl::producer<not_null<DocumentData*>> chosen() const;
|
||||
|
||||
private:
|
||||
using Footer = ChatHelpers::TabbedSelector::InnerFooter;
|
||||
using List = ChatHelpers::TabbedSelector::Inner;
|
||||
using Type = ChatHelpers::SelectorTab;
|
||||
void createSelector(Type type);
|
||||
|
||||
struct Selector {
|
||||
not_null<List*> list;
|
||||
not_null<Footer*> footer;
|
||||
};
|
||||
[[nodiscard]] Selector createEmojiList(
|
||||
not_null<Ui::ScrollArea*> scroll);
|
||||
[[nodiscard]] Selector createStickersList(
|
||||
not_null<Ui::ScrollArea*> scroll) const;
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
base::unique_qptr<Ui::RpWidget> _container;
|
||||
|
||||
rpl::event_stream<> _recentChanges;
|
||||
std::vector<DocumentId> _lastRecent;
|
||||
rpl::event_stream<not_null<DocumentData*>> _chosen;
|
||||
|
||||
};
|
||||
|
||||
EmojiSelector::EmojiSelector(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
rpl::producer<std::vector<DocumentId>> recent)
|
||||
: RpWidget(parent)
|
||||
, _controller(controller) {
|
||||
std::move(
|
||||
recent
|
||||
) | rpl::on_next([=](std::vector<DocumentId> ids) {
|
||||
_lastRecent = std::move(ids);
|
||||
_recentChanges.fire({});
|
||||
}, lifetime());
|
||||
createSelector(Type::Emoji);
|
||||
}
|
||||
|
||||
rpl::producer<not_null<DocumentData*>> EmojiSelector::chosen() const {
|
||||
return _chosen.events();
|
||||
}
|
||||
|
||||
EmojiSelector::Selector EmojiSelector::createEmojiList(
|
||||
not_null<Ui::ScrollArea*> scroll) {
|
||||
const auto session = &_controller->session();
|
||||
const auto manager = &session->data().customEmojiManager();
|
||||
const auto tag = Data::CustomEmojiManager::SizeTag::Large;
|
||||
auto args = ChatHelpers::EmojiListDescriptor{
|
||||
.show = _controller->uiShow(),
|
||||
.mode = ChatHelpers::EmojiListMode::UserpicBuilder,
|
||||
.paused = [=] { return true; },
|
||||
.customRecentList = ChatHelpers::DocumentListToRecent(_lastRecent),
|
||||
.customRecentFactory = [=](DocumentId id, Fn<void()> repaint) {
|
||||
return manager->create(id, std::move(repaint), tag);
|
||||
},
|
||||
.st = &st::userpicBuilderEmojiPan,
|
||||
};
|
||||
const auto list = scroll->setOwnedWidget(
|
||||
object_ptr<ChatHelpers::EmojiListWidget>(scroll, std::move(args)));
|
||||
const auto footer = list->createFooter().data();
|
||||
list->refreshEmoji();
|
||||
list->customChosen(
|
||||
) | rpl::on_next([=](const ChatHelpers::FileChosen &chosen) {
|
||||
_chosen.fire_copy(chosen.document);
|
||||
}, list->lifetime());
|
||||
_recentChanges.events(
|
||||
) | rpl::on_next([=] {
|
||||
createSelector(Type::Emoji);
|
||||
}, list->lifetime());
|
||||
list->setAllowWithoutPremium(true);
|
||||
return { list, footer };
|
||||
}
|
||||
|
||||
EmojiSelector::Selector EmojiSelector::createStickersList(
|
||||
not_null<Ui::ScrollArea*> scroll) const {
|
||||
const auto list = scroll->setOwnedWidget(
|
||||
object_ptr<ChatHelpers::StickersListWidget>(
|
||||
scroll,
|
||||
_controller,
|
||||
Window::GifPauseReason::Any,
|
||||
ChatHelpers::StickersListMode::UserpicBuilder));
|
||||
const auto footer = list->createFooter().data();
|
||||
list->refreshRecent();
|
||||
list->chosen(
|
||||
) | rpl::on_next([=](const ChatHelpers::FileChosen &chosen) {
|
||||
_chosen.fire_copy(chosen.document);
|
||||
}, list->lifetime());
|
||||
return { list, footer };
|
||||
}
|
||||
|
||||
void EmojiSelector::createSelector(Type type) {
|
||||
Expects((type == Type::Emoji) || (type == Type::Stickers));
|
||||
|
||||
const auto isEmoji = (type == Type::Emoji);
|
||||
const auto &stScroll = st::reactPanelScroll;
|
||||
|
||||
_container = base::make_unique_q<Ui::RpWidget>(this);
|
||||
const auto container = _container.get();
|
||||
container->show();
|
||||
sizeValue(
|
||||
) | rpl::on_next([=](const QSize &s) {
|
||||
container->setGeometry(Rect(s));
|
||||
}, container->lifetime());
|
||||
|
||||
const auto scroll = Ui::CreateChild<Ui::ScrollArea>(container, stScroll);
|
||||
|
||||
const auto selector = isEmoji
|
||||
? createEmojiList(scroll)
|
||||
: createStickersList(scroll);
|
||||
selector.footer->setParent(container);
|
||||
|
||||
const auto toggleButton = Ui::CreateChild<Ui::AbstractButton>(container);
|
||||
const auto &togglePos = st::userpicBuilderEmojiSelectorTogglePosition;
|
||||
{
|
||||
const auto &pos = togglePos;
|
||||
toggleButton->resize(st::menuIconStickers.size()
|
||||
// Trying to overlap the settings button under.
|
||||
+ QSize(pos.x() * 2, pos.y() * 2));
|
||||
toggleButton->show();
|
||||
toggleButton->paintRequest(
|
||||
) | rpl::on_next([=] {
|
||||
auto p = QPainter(toggleButton);
|
||||
const auto r = toggleButton->rect()
|
||||
- QMargins(pos.x(), pos.y(), pos.x(), pos.y());
|
||||
p.fillRect(r, st::boxBg);
|
||||
if (isEmoji) {
|
||||
st::userpicBuilderEmojiToggleStickersIcon.paintInCenter(p, r);
|
||||
} else {
|
||||
st::defaultEmojiPan.icons.people.paintInCenter(p, r);
|
||||
}
|
||||
}, toggleButton->lifetime());
|
||||
}
|
||||
toggleButton->show();
|
||||
toggleButton->setClickedCallback([=] {
|
||||
createSelector(isEmoji ? Type::Stickers : Type::Emoji);
|
||||
});
|
||||
|
||||
rpl::combine(
|
||||
scroll->scrollTopValue(),
|
||||
scroll->heightValue()
|
||||
) | rpl::on_next([=](int scrollTop, int scrollHeight) {
|
||||
const auto scrollBottom = scrollTop + scrollHeight;
|
||||
selector.list->setVisibleTopBottom(scrollTop, scrollBottom);
|
||||
}, selector.list->lifetime());
|
||||
|
||||
selector.list->scrollToRequests(
|
||||
) | rpl::on_next([=](int y) {
|
||||
scroll->scrollToY(y);
|
||||
// _shadow->update();
|
||||
}, selector.list->lifetime());
|
||||
|
||||
const auto separator = Ui::CreateChild<Ui::RpWidget>(container);
|
||||
separator->paintRequest(
|
||||
) | rpl::on_next([=](const QRect &r) {
|
||||
auto p = QPainter(separator);
|
||||
p.fillRect(r, st::shadowFg);
|
||||
}, separator->lifetime());
|
||||
|
||||
selector.footer->show();
|
||||
separator->show();
|
||||
scroll->show();
|
||||
|
||||
const auto scrollWidth = stScroll.width;
|
||||
sizeValue(
|
||||
) | rpl::on_next([=](const QSize &s) {
|
||||
const auto left = st::userpicBuilderEmojiSelectorLeft;
|
||||
const auto mostTop = st::userpicBuilderEmojiSelectorLeft;
|
||||
|
||||
toggleButton->move(QPoint(left, mostTop));
|
||||
|
||||
selector.footer->setGeometry(
|
||||
(isEmoji ? (rect::right(toggleButton) - togglePos.x()) : left),
|
||||
mostTop,
|
||||
s.width() - left,
|
||||
selector.footer->height());
|
||||
|
||||
separator->setGeometry(
|
||||
0,
|
||||
rect::bottom(selector.footer),
|
||||
s.width(),
|
||||
st::lineWidth);
|
||||
|
||||
const auto listWidth = s.width() - st::boxRadius * 2;
|
||||
selector.list->resizeToWidth(listWidth);
|
||||
scroll->setGeometry(
|
||||
st::boxRadius,
|
||||
rect::bottom(separator),
|
||||
selector.list->width() + scrollWidth,
|
||||
s.height() - rect::bottom(separator));
|
||||
selector.list->setMinimalHeight(listWidth, scroll->height());
|
||||
}, lifetime());
|
||||
|
||||
// Reset all animations.
|
||||
selector.list->hideFinished();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
not_null<Ui::VerticalLayout*> CreateUserpicBuilder(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
StartData data,
|
||||
BothWayCommunication<UserpicBuilder::Result> communication) {
|
||||
const auto container = Ui::CreateChild<Ui::VerticalLayout>(parent.get());
|
||||
|
||||
struct State {
|
||||
std::vector<not_null<CircleButton*>> circleButtons;
|
||||
Ui::Animations::Simple chosenColorAnimation;
|
||||
int colorIndex = -1;
|
||||
|
||||
std::vector<QColor> editorColors;
|
||||
StartData gradientEditorStartData;
|
||||
};
|
||||
const auto state = container->lifetime().make_state<State>();
|
||||
|
||||
const auto preview = container->add(
|
||||
object_ptr<EmojiUserpic>(
|
||||
container,
|
||||
Size(st::settingsInfoPhotoSize),
|
||||
data.isForum),
|
||||
st::userpicBuilderEmojiPreviewPadding,
|
||||
style::al_top);
|
||||
if (const auto id = data.documentId) {
|
||||
const auto document = controller->session().data().document(id);
|
||||
if (document && document->sticker()) {
|
||||
preview->setDocument(document);
|
||||
}
|
||||
}
|
||||
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
tr::lng_userpic_builder_color_subtitle(),
|
||||
st::userpicBuilderEmojiSubtitle),
|
||||
st::userpicBuilderEmojiSubtitlePadding,
|
||||
style::al_top);
|
||||
|
||||
const auto paletteBg = Ui::AddBubbleWrap(
|
||||
container,
|
||||
QSize(
|
||||
st::userpicBuilderEmojiBubblePaletteWidth,
|
||||
std::abs(Ui::BubbleWrapInnerRect(QRect(0, 0, 0, 0)).height())
|
||||
+ st::userpicBuilderEmojiAccentColorSize
|
||||
+ rect::m::sum::v(
|
||||
st::userpicBuilderEmojiBubblePalettePadding)));
|
||||
const auto palette = Ui::CreateChild<Ui::VerticalLayout>(paletteBg.get());
|
||||
{
|
||||
constexpr auto kColorsCount = int(7);
|
||||
const auto checkIsSpecial = [=](int i) {
|
||||
return (i == kColorsCount);
|
||||
};
|
||||
const auto size = st::userpicBuilderEmojiAccentColorSize;
|
||||
const auto paletteGradients = PaletteGradients();
|
||||
for (auto i = 0; i < kColorsCount + 1; i++) {
|
||||
const auto isSpecial = checkIsSpecial(i);
|
||||
const auto colors = paletteGradients[i % kColorsCount];
|
||||
const auto button = Ui::CreateChild<CircleButton>(palette);
|
||||
state->circleButtons.push_back(button);
|
||||
button->resize(size, size);
|
||||
button->setBrush(isSpecial
|
||||
? GenerateSpecial(size, state->editorColors)
|
||||
: GenerateGradient(Size(size), colors));
|
||||
|
||||
const auto openEditor = isSpecial
|
||||
? Fn<void()>([=] {
|
||||
if (checkIsSpecial(state->colorIndex)) {
|
||||
state->colorIndex = -1;
|
||||
}
|
||||
ShowGradientEditor(
|
||||
controller,
|
||||
state->gradientEditorStartData,
|
||||
[=](std::vector<QColor> colors) {
|
||||
state->editorColors = std::move(colors);
|
||||
button->setBrush(
|
||||
GenerateSpecial(size, state->editorColors));
|
||||
button->clicked({}, Qt::LeftButton);
|
||||
});
|
||||
})
|
||||
: nullptr;
|
||||
|
||||
button->setClickedCallback([=] {
|
||||
if (openEditor && state->editorColors.empty()) {
|
||||
return openEditor();
|
||||
}
|
||||
const auto was = state->colorIndex;
|
||||
const auto now = i;
|
||||
if (was == now) {
|
||||
if (openEditor) {
|
||||
openEditor();
|
||||
}
|
||||
return;
|
||||
}
|
||||
state->chosenColorAnimation.stop();
|
||||
state->chosenColorAnimation.start([=](float64 progress) {
|
||||
if (was >= 0) {
|
||||
state->circleButtons[was]->setSelectedProgress(
|
||||
1. - progress);
|
||||
}
|
||||
state->circleButtons[now]->setSelectedProgress(progress);
|
||||
}, 0., 1., st::universalDuration);
|
||||
state->colorIndex = now;
|
||||
|
||||
const auto result = isSpecial
|
||||
? state->editorColors
|
||||
: colors;
|
||||
state->gradientEditorStartData.gradientEditorColors = result;
|
||||
preview->setGradientColors(result);
|
||||
});
|
||||
}
|
||||
const auto current = data.builderColorIndex % kColorsCount;
|
||||
state->circleButtons[current]->setSelectedProgress(1.);
|
||||
state->circleButtons[current]->clicked({}, Qt::LeftButton);
|
||||
}
|
||||
paletteBg->sizeValue(
|
||||
) | rpl::on_next([=](const QSize &s) {
|
||||
palette->setGeometry(Ui::BubbleWrapInnerRect(Rect(s))
|
||||
- st::userpicBuilderEmojiBubblePalettePadding);
|
||||
AlignChildren(palette, palette->width());
|
||||
}, palette->lifetime());
|
||||
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
tr::lng_userpic_builder_emoji_subtitle(),
|
||||
st::userpicBuilderEmojiSubtitle),
|
||||
st::userpicBuilderEmojiSubtitlePadding,
|
||||
style::al_top);
|
||||
|
||||
const auto selectorBg = Ui::AddBubbleWrap(
|
||||
container,
|
||||
QSize(
|
||||
st::userpicBuilderEmojiBubblePaletteWidth,
|
||||
st::userpicBuilderEmojiSelectorMinHeight));
|
||||
const auto selector = Ui::CreateChild<EmojiSelector>(
|
||||
selectorBg.get(),
|
||||
controller,
|
||||
base::take(data.documents));
|
||||
selector->chosen(
|
||||
) | rpl::on_next([=](not_null<DocumentData*> document) {
|
||||
state->gradientEditorStartData.documentId = document->id;
|
||||
preview->setDocument(document);
|
||||
}, preview->lifetime());
|
||||
selectorBg->sizeValue(
|
||||
) | rpl::on_next([=](const QSize &s) {
|
||||
selector->setGeometry(Ui::BubbleWrapInnerRect(Rect(s)));
|
||||
}, selector->lifetime());
|
||||
|
||||
base::take(
|
||||
communication.triggers
|
||||
) | rpl::on_next([=, done = base::take(communication.result)] {
|
||||
preview->result(Editor::kProfilePhotoSize, [=](Result result) {
|
||||
done(std::move(result));
|
||||
});
|
||||
}, preview->lifetime());
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> CreateEmojiUserpic(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const QSize &size,
|
||||
rpl::producer<not_null<DocumentData*>> document,
|
||||
rpl::producer<int> colorIndex,
|
||||
bool isForum) {
|
||||
const auto paletteGradients = PaletteGradients();
|
||||
const auto widget = Ui::CreateChild<EmojiUserpic>(
|
||||
parent.get(),
|
||||
size,
|
||||
isForum);
|
||||
std::move(
|
||||
document
|
||||
) | rpl::on_next([=](not_null<DocumentData*> d) {
|
||||
widget->setDocument(d);
|
||||
}, widget->lifetime());
|
||||
std::move(
|
||||
colorIndex
|
||||
) | rpl::on_next([=](int index) {
|
||||
widget->setGradientColors(
|
||||
paletteGradients[index % paletteGradients.size()]);
|
||||
}, widget->lifetime());
|
||||
return widget;
|
||||
}
|
||||
|
||||
} // namespace UserpicBuilder
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
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
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
class VerticalLayout;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace UserpicBuilder {
|
||||
|
||||
struct StartData;
|
||||
struct Result;
|
||||
|
||||
template <typename Result>
|
||||
struct BothWayCommunication;
|
||||
|
||||
not_null<Ui::VerticalLayout*> CreateUserpicBuilder(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
StartData data,
|
||||
BothWayCommunication<UserpicBuilder::Result> communication);
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> CreateEmojiUserpic(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const QSize &size,
|
||||
rpl::producer<not_null<DocumentData*>> document,
|
||||
rpl::producer<int> colorIndex,
|
||||
bool isForum);
|
||||
|
||||
} // namespace UserpicBuilder
|
||||
Reference in New Issue
Block a user