Make nice emoji status selector in profile.

This commit is contained in:
John Preston 2022-08-11 13:10:40 +03:00
parent 165d3143de
commit 64bd4f0926
10 changed files with 327 additions and 229 deletions

View file

@ -364,8 +364,14 @@ void EmojiColorPicker::drawVariant(Painter &p, int variant) {
EmojiListWidget::EmojiListWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,
Window::GifPauseReason level)
Window::GifPauseReason level,
Mode mode)
: Inner(parent, controller, level)
, _mode(mode)
, _staticCount(_mode == Mode::Full ? kEmojiSectionCount : 1)
, _premiumIcon(_mode == Mode::EmojiStatus
? std::make_unique<GradientPremiumStar>()
: nullptr)
, _localSetsManager(
std::make_unique<LocalStickersManager>(&controller->session()))
, _collapsedBg(st::emojiPanExpand.height / 2, st::emojiPanHeaderFg)
@ -376,7 +382,7 @@ EmojiListWidget::EmojiListWidget(
_picker->hide();
for (auto i = 1; i != kEmojiSectionCount; ++i) {
for (auto i = 1; i != _staticCount; ++i) {
const auto section = static_cast<Section>(i);
_counts[i] = Ui::Emoji::GetSectionCount(section);
}
@ -430,8 +436,8 @@ void EmojiListWidget::repaintCustom(uint64 setId) {
const auto repaint1 = repaintRecent
&& (info.section == int(Section::Recent));
const auto repaint2 = !repaint1
&& (info.section >= kEmojiSectionCount)
&& (setId == _custom[info.section - kEmojiSectionCount].id);
&& (info.section >= _staticCount)
&& (setId == _custom[info.section - _staticCount].id);
if (repaint1 || repaint2) {
update(
0,
@ -496,10 +502,10 @@ void EmojiListWidget::unloadCustomIn(const SectionInfo &info) {
}
}
return;
} else if (info.section < kEmojiSectionCount) {
} else if (info.section < _staticCount) {
return;
}
auto &custom = _custom[info.section - kEmojiSectionCount];
auto &custom = _custom[info.section - _staticCount];
if (!custom.painted) {
return;
}
@ -549,7 +555,7 @@ bool EmojiListWidget::enumerateSections(Callback callback) const {
info.top = info.rowsBottom;
return true;
};
for (; i != kEmojiSectionCount; ++i) {
for (; i != _staticCount; ++i) {
info.section = i;
info.count = i ? _counts[i] : _recent.size();
if (!next()) {
@ -599,7 +605,7 @@ EmojiListWidget::SectionInfo EmojiListWidget::sectionInfoByOffset(
}
int EmojiListWidget::sectionsCount() const {
return kEmojiSectionCount + int(_custom.size());
return _staticCount + int(_custom.size());
}
void EmojiListWidget::setSingleSize(QSize size) {
@ -612,6 +618,9 @@ void EmojiListWidget::setSingleSize(QSize size) {
_innerPosition = QPoint(
(area.width() - esize) / 2,
(area.height() - esize) / 2);
const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize);
const auto customSkip = (esize - customSize) / 2;
_customPosition = QPoint(customSkip, customSkip);
_picker->setSingleSize(_singleSize);
}
@ -649,7 +658,7 @@ void EmojiListWidget::ensureLoaded(int section) {
fillRecent();
}
return;
} else if (section >= kEmojiSectionCount || !_emoji[section].empty()) {
} else if (section >= _staticCount || !_emoji[section].empty()) {
return;
}
_emoji[section] = Ui::Emoji::GetSection(static_cast<Section>(section));
@ -671,11 +680,17 @@ void EmojiListWidget::fillRecent() {
_recentCustomIds.clear();
const auto &list = Core::App().settings().recentEmoji();
_recent.reserve(std::min(int(list.size()), Core::kRecentEmojiLimit));
_recent.reserve(std::min(int(list.size()), Core::kRecentEmojiLimit) + 1);
if (_mode == Mode::EmojiStatus) {
const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f");
_recent.push_back({ .id = { Ui::Emoji::Find(star) } });
}
const auto test = controller()->session().isTestMode();
for (const auto &one : list) {
const auto document = std::get_if<RecentEmojiDocument>(&one.id.data);
if (document && document->test != test) {
if (_mode == Mode::EmojiStatus && !document) {
continue;
} else if (document && document->test != test) {
continue;
}
_recent.push_back({
@ -730,9 +745,9 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
if (info.section > 0 && r.top() < info.rowsTop) {
p.setFont(st::emojiPanHeaderFont);
p.setPen(st::emojiPanHeaderFg);
auto titleText = (info.section < kEmojiSectionCount)
auto titleText = (info.section < _staticCount)
? ChatHelpers::EmojiCategoryTitle(info.section)(tr::now)
: _custom[info.section - kEmojiSectionCount].title;
: _custom[info.section - _staticCount].title;
auto titleWidth = st::emojiPanHeaderFont->width(titleText);
if (titleWidth > widthForTitle) {
titleText = st::emojiPanHeaderFont->elided(titleText, widthForTitle);
@ -793,10 +808,10 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
}
if (info.section == int(Section::Recent)) {
drawRecent(p, w, now, paused, index);
} else if (info.section < kEmojiSectionCount) {
} else if (info.section < _staticCount) {
drawEmoji(p, w, _emoji[info.section][index]);
} else {
const auto set = info.section - kEmojiSectionCount;
const auto set = info.section - _staticCount;
drawCustom(p, w, now, paused, set, index);
}
}
@ -834,10 +849,18 @@ void EmojiListWidget::drawRecent(
int index) {
_recentPainted = true;
if (const auto emoji = std::get_if<EmojiPtr>(&_recent[index].id.data)) {
drawEmoji(p, position, *emoji);
if (_mode == Mode::EmojiStatus) {
position += QPoint(
(_singleSize.width() - st::stickersPremium.width()) / 2,
(_singleSize.height() - st::stickersPremium.height()) / 2
) - _areaPosition;
p.drawImage(position, _premiumIcon->image());
} else {
drawEmoji(p, position, *emoji);
}
} else {
Assert(_recent[index].custom != nullptr);
position += _innerPosition;
position += _innerPosition + _customPosition;
_recent[index].custom->paint(
p,
position.x(),
@ -868,7 +891,7 @@ void EmojiListWidget::drawCustom(
bool paused,
int set,
int index) {
position += _innerPosition;
position += _innerPosition + _customPosition;
_custom[set].painted = true;
_custom[set].list[index].custom->paint(
p,
@ -897,7 +920,7 @@ EmojiPtr EmojiListWidget::lookupOverEmoji(const OverEmoji *over) const {
&& v::is<EmojiPtr>(_recent[index].id.data))
? v::get<EmojiPtr>(_recent[index].id.data)
: (section > int(Section::Recent)
&& section < kEmojiSectionCount
&& section < _staticCount
&& index < _emoji[section].size())
? _emoji[section][index]
: nullptr;
@ -958,10 +981,10 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
if (const auto over = std::get_if<OverEmoji>(&_selected)) {
const auto section = over->section;
const auto index = over->index;
if (section >= kEmojiSectionCount
if (section >= _staticCount
&& sectionInfo(section).collapsed
&& index + 1 == _columnCount * kCollapsedRows) {
_custom[section - kEmojiSectionCount].expanded = true;
_custom[section - _staticCount].expanded = true;
resizeToWidth(width());
update();
return;
@ -980,19 +1003,19 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
if (custom && custom->sticker()) {
selectCustom(custom);
}
} else if (section >= kEmojiSectionCount
&& index < _custom[section - kEmojiSectionCount].list.size()) {
auto &set = _custom[section - kEmojiSectionCount];
} else if (section >= _staticCount
&& index < _custom[section - _staticCount].list.size()) {
auto &set = _custom[section - _staticCount];
selectCustom(set.list[index].document);
}
} else if (const auto set = std::get_if<OverSet>(&pressed)) {
Assert(set->section >= kEmojiSectionCount
&& set->section < kEmojiSectionCount + _custom.size());
displaySet(_custom[set->section - kEmojiSectionCount].id);
Assert(set->section >= _staticCount
&& set->section < _staticCount + _custom.size());
displaySet(_custom[set->section - _staticCount].id);
} else if (auto button = std::get_if<OverButton>(&pressed)) {
Assert(button->section >= kEmojiSectionCount
&& button->section < kEmojiSectionCount + _custom.size());
const auto id = _custom[button->section - kEmojiSectionCount].id;
Assert(button->section >= _staticCount
&& button->section < _staticCount + _custom.size());
const auto id = _custom[button->section - _staticCount].id;
if (hasRemoveButton(button->section)) {
removeSet(id);
} else if (hasAddButton(button->section)) {
@ -1074,11 +1097,11 @@ void EmojiListWidget::pickerHidden() {
}
bool EmojiListWidget::hasRemoveButton(int index) const {
if (index < kEmojiSectionCount
|| index >= kEmojiSectionCount + _custom.size()) {
if (index < _staticCount
|| index >= _staticCount + _custom.size()) {
return false;
}
const auto &set = _custom[index - kEmojiSectionCount];
const auto &set = _custom[index - _staticCount];
return set.canRemove && !set.premiumRequired;
}
@ -1096,11 +1119,11 @@ QRect EmojiListWidget::removeButtonRect(const SectionInfo &info) const {
}
bool EmojiListWidget::hasAddButton(int index) const {
if (index < kEmojiSectionCount
|| index >= kEmojiSectionCount + _custom.size()) {
if (index < _staticCount
|| index >= _staticCount + _custom.size()) {
return false;
}
const auto &set = _custom[index - kEmojiSectionCount];
const auto &set = _custom[index - _staticCount];
return !set.canRemove && !set.premiumRequired;
}
@ -1109,24 +1132,24 @@ QRect EmojiListWidget::addButtonRect(int index) const {
}
bool EmojiListWidget::hasUnlockButton(int index) const {
if (index < kEmojiSectionCount
|| index >= kEmojiSectionCount + _custom.size()) {
if (index < _staticCount
|| index >= _staticCount + _custom.size()) {
return false;
}
const auto &set = _custom[index - kEmojiSectionCount];
const auto &set = _custom[index - _staticCount];
return set.premiumRequired;
}
QRect EmojiListWidget::unlockButtonRect(int index) const {
Expects(index >= kEmojiSectionCount
&& index < kEmojiSectionCount + _custom.size());
Expects(index >= _staticCount
&& index < _staticCount + _custom.size());
return buttonRect(sectionInfo(index), rightButton(index));
}
bool EmojiListWidget::hasButton(int index) const {
if (index < kEmojiSectionCount
|| index >= kEmojiSectionCount + _custom.size()) {
if (index < _staticCount
|| index >= _staticCount + _custom.size()) {
return false;
}
return true;
@ -1151,11 +1174,11 @@ QRect EmojiListWidget::buttonRect(
}
auto EmojiListWidget::rightButton(int index) const -> const RightButton & {
Expects(index >= kEmojiSectionCount
&& index < kEmojiSectionCount + _custom.size());
Expects(index >= _staticCount
&& index < _staticCount + _custom.size());
return hasAddButton(index)
? _add
: _custom[index - kEmojiSectionCount].canRemove
: _custom[index - _staticCount].canRemove
? _restore
: _unlock;
}
@ -1187,7 +1210,7 @@ void EmojiListWidget::colorChosen(EmojiPtr emoji) {
const auto over = std::get_if<OverEmoji>(&_pickerSelected);
if (over
&& over->section > int(Section::Recent)
&& over->section < kEmojiSectionCount
&& over->section < _staticCount
&& over->index < _emoji[over->section].size()) {
_emoji[over->section][over->index] = emoji;
rtlupdate(emojiRect(over->section, over->index));
@ -1466,7 +1489,8 @@ std::vector<StickerIcon> EmojiListWidget::fillIcons() {
result.reserve(2 + _custom.size());
result.emplace_back(RecentEmojiSectionSetId());
if (_custom.empty()) {
if (_mode == Mode::EmojiStatus) {
} else if (_custom.empty()) {
using Section = Ui::Emoji::Section;
for (auto i = int(Section::People); i <= int(Section::Symbols); ++i) {
result.emplace_back(EmojiSectionSetId(Section(i)));
@ -1489,11 +1513,11 @@ int EmojiListWidget::paintButtonGetWidth(
const SectionInfo &info,
bool selected,
QRect clip) const {
if (info.section < kEmojiSectionCount
|| info.section >= kEmojiSectionCount + _custom.size()) {
if (info.section < _staticCount
|| info.section >= _staticCount + _custom.size()) {
return 0;
}
auto &custom = _custom[info.section - kEmojiSectionCount];
auto &custom = _custom[info.section - _staticCount];
if (hasRemoveButton(info.section)) {
const auto remove = removeButtonRect(info);
if (remove.intersects(clip)) {
@ -1565,7 +1589,7 @@ void EmojiListWidget::updateSelected() {
if (hasButton(section)
&& myrtlrect(buttonRect(section)).contains(p.x(), p.y())) {
newSelected = OverButton{ section };
} else if (section >= kEmojiSectionCount) {
} else if (section >= _staticCount) {
newSelected = OverSet{ section };
}
} else if (p.y() >= info.rowsTop && p.y() < info.rowsBottom) {
@ -1616,18 +1640,18 @@ void EmojiListWidget::setSelected(OverState newSelected) {
void EmojiListWidget::setPressed(OverState newPressed) {
if (auto button = std::get_if<OverButton>(&_pressed)) {
Assert(button->section >= kEmojiSectionCount
&& button->section < kEmojiSectionCount + _custom.size());
auto &set = _custom[button->section - kEmojiSectionCount];
Assert(button->section >= _staticCount
&& button->section < _staticCount + _custom.size());
auto &set = _custom[button->section - _staticCount];
if (set.ripple) {
set.ripple->lastStop();
}
}
_pressed = newPressed;
if (auto button = std::get_if<OverButton>(&_pressed)) {
Assert(button->section >= kEmojiSectionCount
&& button->section < kEmojiSectionCount + _custom.size());
auto &set = _custom[button->section - kEmojiSectionCount];
Assert(button->section >= _staticCount
&& button->section < _staticCount + _custom.size());
auto &set = _custom[button->section - _staticCount];
if (!set.ripple) {
set.ripple = createButtonRipple(button->section);
}
@ -1675,8 +1699,8 @@ void EmojiListWidget::initButton(
std::unique_ptr<Ui::RippleAnimation> EmojiListWidget::createButtonRipple(
int section) {
Expects(section >= kEmojiSectionCount
&& section < kEmojiSectionCount + _custom.size());
Expects(section >= _staticCount
&& section < _staticCount + _custom.size());
const auto remove = hasRemoveButton(section);
const auto &st = remove
@ -1694,8 +1718,8 @@ std::unique_ptr<Ui::RippleAnimation> EmojiListWidget::createButtonRipple(
}
QPoint EmojiListWidget::buttonRippleTopLeft(int section) const {
Expects(section >= kEmojiSectionCount
&& section < kEmojiSectionCount + _custom.size());
Expects(section >= _staticCount
&& section < _staticCount + _custom.size());
return myrtlrect(buttonRect(section)).topLeft()
+ (hasRemoveButton(section)
@ -1727,12 +1751,12 @@ void EmojiListWidget::showSet(uint64 setId) {
}
uint64 EmojiListWidget::sectionSetId(int section) const {
Expects(section < kEmojiSectionCount
|| (section - kEmojiSectionCount) < _custom.size());
Expects(section < _staticCount
|| (section - _staticCount) < _custom.size());
return (section < kEmojiSectionCount)
return (section < _staticCount)
? EmojiSectionSetId(static_cast<Section>(section))
: _custom[section - kEmojiSectionCount].id;
: _custom[section - _staticCount].id;
}
tr::phrase<> EmojiCategoryTitle(int index) {

View file

@ -50,16 +50,22 @@ inline constexpr auto kEmojiSectionCount = 8;
struct StickerIcon;
class EmojiColorPicker;
class StickersListFooter;
class GradientPremiumStar;
class LocalStickersManager;
class EmojiListWidget
: public TabbedSelector::Inner
, public Ui::AbstractTooltipShower {
public:
enum class Mode {
Full,
EmojiStatus,
};
EmojiListWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,
Window::GifPauseReason level);
Window::GifPauseReason level,
Mode mode);
~EmojiListWidget();
using Section = Ui::Emoji::Section;
@ -266,7 +272,10 @@ private:
DocumentId documentId,
uint64 setId);
Mode _mode = Mode::Full;
const int _staticCount = 0;
StickersListFooter *_footer = nullptr;
std::unique_ptr<GradientPremiumStar> _premiumIcon;
std::unique_ptr<LocalStickersManager> _localSetsManager;
int _counts[kEmojiSectionCount];
@ -284,6 +293,7 @@ private:
QSize _singleSize;
QPoint _areaPosition;
QPoint _innerPosition;
QPoint _customPosition;
RightButton _add;
RightButton _unlock;

View file

@ -145,6 +145,42 @@ bool StickersListFooter::ScrollState::animationCallback(crl::time now) {
return true;
}
GradientPremiumStar::GradientPremiumStar() {
style::PaletteChanged(
) | rpl::start_with_next([=] {
_image = QImage();
}, _lifetime);
}
QImage GradientPremiumStar::image() const {
if (_image.isNull()) {
renderOnDemand();
}
return _image;
}
void GradientPremiumStar::renderOnDemand() const {
const auto size = st::stickersPremium.size();
const auto mask = st::stickersPremium.instance(Qt::white);
const auto factor = style::DevicePixelRatio();
_image = QImage(
size * factor,
QImage::Format_ARGB32_Premultiplied);
_image.setDevicePixelRatio(factor);
QPainter p(&_image);
auto gradient = QLinearGradient(
QPoint(0, size.height()),
QPoint(size.width(), 0));
gradient.setStops({
{ 0., st::stickerPanPremium1->c },
{ 1., st::stickerPanPremium2->c },
});
p.fillRect(QRect(QPoint(), size), gradient);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.drawImage(QRect(QPoint(), size), mask);
}
StickersListFooter::StickersListFooter(Descriptor &&descriptor)
: InnerFooter(descriptor.parent)
, _controller(descriptor.controller)
@ -169,36 +205,6 @@ StickersListFooter::StickersListFooter(Descriptor &&descriptor)
) | rpl::start_with_next([=] {
update();
}, lifetime());
style::PaletteChanged(
) | rpl::start_with_next([=] {
_premiumIcon = QImage();
}, lifetime());
}
void StickersListFooter::validatePremiumIcon() const {
if (!_premiumIcon.isNull()) {
return;
}
const auto size = st::stickersPremium.size();
const auto mask = st::stickersPremium.instance(Qt::white);
const auto factor = style::DevicePixelRatio();
_premiumIcon = QImage(
size * factor,
QImage::Format_ARGB32_Premultiplied);
_premiumIcon.setDevicePixelRatio(factor);
QPainter p(&_premiumIcon);
auto gradient = QLinearGradient(
QPoint(0, size.height()),
QPoint(size.width(), 0));
gradient.setStops({
{ 0., st::stickerPanPremium1->c },
{ 1., st::stickerPanPremium2->c },
});
p.fillRect(QRect(QPoint(), size), gradient);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.drawImage(QRect(QPoint(), size), mask);
}
void StickersListFooter::clearHeavyData() {
@ -1211,12 +1217,11 @@ void StickersListFooter::paintSetIcon(
width(),
st::stickerGroupCategorySize);
} else if (icon.setId == Data::Stickers::PremiumSetId) {
validatePremiumIcon();
const auto size = st::stickersPremium.size();
p.drawImage(
info.adjustedLeft + (_singleWidth - size.width()) / 2,
_iconsTop + (st::emojiFooterHeight - size.height()) / 2,
_premiumIcon);
_premiumIcon.image());
} else {
using Section = Ui::Emoji::Section;
const auto sectionIcon = [&](Section section, bool active) {

View file

@ -75,6 +75,20 @@ struct StickerIcon {
mutable rpl::lifetime lifetime;
};
class GradientPremiumStar {
public:
GradientPremiumStar();
[[nodiscard]] QImage image() const;
private:
void renderOnDemand() const;
mutable QImage _image;
rpl::lifetime _lifetime;
};
class StickersListFooter final : public TabbedSelector::InnerFooter {
public:
struct Descriptor {
@ -203,7 +217,6 @@ private:
void paintSelectionBg(Painter &p) const;
void paintSelectionBar(Painter &p) const;
void paintLeftRightFading(Painter &p) const;
void validatePremiumIcon() const;
void updateEmojiSectionWidth();
void updateEmojiWidthCallback();
@ -230,7 +243,7 @@ private:
OverState _pressed = SpecialOver::None;
QPoint _iconsMousePos, _iconsMouseDown;
mutable QImage _premiumIcon;
GradientPremiumStar _premiumIcon;
int _iconsLeft = 0;
int _iconsRight = 0;
int _iconsTop = 0;

View file

@ -132,7 +132,19 @@ void TabbedPanel::moveBottomRight(int bottom, int right) {
_right = right;
// If the panel is already shown, update the position.
if (!isHidden() && isNew) {
moveByBottom();
moveHorizontally();
} else {
updateContentHeight();
}
}
void TabbedPanel::moveTopRight(int top, int right) {
const auto isNew = (_top != top || _right != right);
_top = top;
_right = right;
// If the panel is already shown, update the position.
if (!isHidden() && isNew) {
moveHorizontally();
} else {
updateContentHeight();
}
@ -148,16 +160,26 @@ void TabbedPanel::setDesiredHeightValues(
updateContentHeight();
}
void TabbedPanel::setDropDown(bool dropDown) {
selector()->setDropDown(dropDown);
_dropDown = dropDown;
}
void TabbedPanel::updateContentHeight() {
auto addedHeight = innerPadding().top() + innerPadding().bottom();
auto marginsHeight = _selector->marginTop() + _selector->marginBottom();
auto availableHeight = _bottom - marginsHeight;
auto wantedContentHeight = qRound(_heightRatio * availableHeight) - addedHeight;
auto availableHeight = _dropDown
? (parentWidget()->height() - _top - marginsHeight)
: (_bottom - marginsHeight);
auto wantedContentHeight = qRound(_heightRatio * availableHeight)
- addedHeight;
auto contentHeight = marginsHeight + std::clamp(
wantedContentHeight,
_minContentHeight,
_maxContentHeight);
auto resultTop = _bottom - addedHeight - contentHeight;
auto resultTop = _dropDown
? _top
: (_bottom - addedHeight - contentHeight);
if (contentHeight == _contentHeight) {
move(x(), resultTop);
return;
@ -204,7 +226,7 @@ void TabbedPanel::paintEvent(QPaintEvent *e) {
}
}
void TabbedPanel::moveByBottom() {
void TabbedPanel::moveHorizontally() {
const auto right = std::max(parentWidget()->width() - _right, 0);
moveToRight(right, y());
updateContentHeight();
@ -318,7 +340,7 @@ void TabbedPanel::startShowAnimation() {
if (!_a_show.animating()) {
auto image = grabForAnimation();
_showAnimation = std::make_unique<Ui::PanelAnimation>(st::emojiPanAnimation, Ui::PanelAnimation::Origin::BottomRight);
_showAnimation = std::make_unique<Ui::PanelAnimation>(st::emojiPanAnimation, _dropDown ? Ui::PanelAnimation::Origin::TopRight : Ui::PanelAnimation::Origin::BottomRight);
auto inner = rect().marginsRemoved(st::emojiPanMargins);
_showAnimation->setFinalImage(std::move(image), QRect(inner.topLeft() * cIntRetinaFactor(), inner.size() * cIntRetinaFactor()));
_showAnimation->setCornerMasks(Images::CornersMask(ImageRoundRadius::Small));
@ -402,7 +424,7 @@ void TabbedPanel::showStarted() {
}
if (isHidden()) {
_selector->showStarted();
moveByBottom();
moveHorizontally();
raise();
show();
startShowAnimation();
@ -424,7 +446,7 @@ bool TabbedPanel::eventFilter(QObject *obj, QEvent *e) {
void TabbedPanel::showFromSelector() {
if (isHidden()) {
moveByBottom();
moveHorizontally();
startShowAnimation();
show();
}

View file

@ -41,10 +41,12 @@ public:
[[nodiscard]] not_null<TabbedSelector*> selector() const;
void moveBottomRight(int bottom, int right);
void moveTopRight(int top, int right);
void setDesiredHeightValues(
float64 ratio,
int minHeight,
int maxHeight);
void setDropDown(bool dropDown);
void hideFast();
bool hiding() const {
@ -76,7 +78,7 @@ private:
TabbedSelector *nonOwnedSelector);
void hideByTimerOrLeave();
void moveByBottom();
void moveHorizontally();
void showFromSelector();
style::margins innerPadding() const;
@ -103,6 +105,7 @@ private:
int _contentMaxHeight = 0;
int _contentHeight = 0;
int _top = 0;
int _bottom = 0;
int _right = 0;
float64 _heightRatio = 1.;
@ -113,6 +116,7 @@ private:
Ui::Animations::Simple _a_show;
bool _shouldFinishHide = false;
bool _dropDown = false;
bool _hiding = false;
bool _hideAfterSlide = false;

View file

@ -439,7 +439,14 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
auto createWidget = [&]() -> object_ptr<Inner> {
switch (type) {
case SelectorTab::Emoji:
return object_ptr<EmojiListWidget>(this, _controller, _level);
using EmojiMode = EmojiListWidget::Mode;
return object_ptr<EmojiListWidget>(
this,
_controller,
_level,
(_mode == Mode::EmojiStatus
? EmojiMode::EmojiStatus
: EmojiMode::Full));
case SelectorTab::Stickers:
return object_ptr<StickersListWidget>(this, _controller, _level);
case SelectorTab::Gifs:
@ -561,7 +568,7 @@ void TabbedSelector::resizeEvent(QResizeEvent *e) {
}
auto scrollWidth = width() - st::roundRadiusSmall;
auto scrollHeight = height() - scrollTop() - marginBottom();
auto scrollHeight = height() - scrollTop() - scrollBottom();
auto inner = currentTab()->widget();
auto innerWidth = scrollWidth - st::emojiScroll.width;
auto updateScrollGeometry = [&] {
@ -591,7 +598,7 @@ void TabbedSelector::resizeEvent(QResizeEvent *e) {
st::lineWidth);
updateRestrictedLabelGeometry();
_footerTop = height() - st::emojiFooterHeight;
_footerTop = _dropDown ? 0 : (height() - st::emojiFooterHeight);
for (auto &tab : _tabs) {
tab.footer()->resizeToWidth(width());
tab.footer()->moveToLeft(0, _footerTop);
@ -630,21 +637,7 @@ void TabbedSelector::paintEvent(QPaintEvent *e) {
void TabbedSelector::paintSlideFrame(Painter &p) {
if (_roundRadius > 0) {
const auto topPart = QRect(
0,
0,
width(),
_tabsSlider
? _tabsSlider->height() + _roundRadius
: 3 * _roundRadius);
Ui::FillRoundRect(
p,
topPart,
st::emojiPanBg,
ImageRoundRadius::Small,
tabbed()
? RectPart::FullTop | RectPart::NoTopBottom
: RectPart::FullTop);
paintBgRoundedPart(p);
} else if (_tabsSlider) {
p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg);
}
@ -652,43 +645,53 @@ void TabbedSelector::paintSlideFrame(Painter &p) {
_slideAnimation->paintFrame(p, slideDt, 1.);
}
void TabbedSelector::paintContent(Painter &p) {
auto &bottomBg = hasSectionIcons()
? st::emojiPanCategories
: st::emojiPanBg;
if (_roundRadius > 0) {
const auto topPart = QRect(
void TabbedSelector::paintBgRoundedPart(Painter &p) {
const auto threeRadius = 3 * _roundRadius;
const auto topOrBottomPart = _dropDown
? QRect(0, height() - threeRadius, width(), threeRadius)
: QRect(
0,
0,
width(),
_tabsSlider
(_tabsSlider
? _tabsSlider->height() + _roundRadius
: 3 * _roundRadius);
Ui::FillRoundRect(
p,
topPart,
st::emojiPanBg,
ImageRoundRadius::Small,
tabbed()
? RectPart::FullTop | RectPart::NoTopBottom
: RectPart::FullTop);
: threeRadius));
Ui::FillRoundRect(
p,
topOrBottomPart,
st::emojiPanBg,
ImageRoundRadius::Small,
(_dropDown
? RectPart::FullBottom
: tabbed()
? (RectPart::FullTop | RectPart::NoTopBottom)
: RectPart::FullTop));
}
const auto bottomPart = QRect(
void TabbedSelector::paintContent(Painter &p) {
auto &footerBg = hasSectionIcons()
? st::emojiPanCategories
: st::emojiPanBg;
if (_roundRadius > 0) {
paintBgRoundedPart(p);
const auto footerPart = QRect(
0,
_footerTop - _roundRadius,
_footerTop - (_dropDown ? 0 : _roundRadius),
width(),
st::emojiFooterHeight + _roundRadius);
Ui::FillRoundRect(
p,
bottomPart,
bottomBg,
footerPart,
footerBg,
ImageRoundRadius::Small,
RectPart::NoTopBottom | RectPart::FullBottom);
(RectPart::NoTopBottom
| (_dropDown ? RectPart::FullTop : RectPart::FullBottom)));
} else {
if (_tabsSlider) {
p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg);
}
p.fillRect(0, _footerTop, width(), st::emojiFooterHeight, bottomBg);
p.fillRect(0, _footerTop, width(), st::emojiFooterHeight, footerBg);
}
auto sidesTop = marginTop();
@ -710,17 +713,23 @@ void TabbedSelector::paintContent(Painter &p) {
}
int TabbedSelector::marginTop() const {
return _tabsSlider
return _dropDown
? st::emojiFooterHeight
: _tabsSlider
? (_tabsSlider->height() - st::lineWidth)
: _roundRadius;
}
int TabbedSelector::scrollTop() const {
return tabbed() ? marginTop() : 0;
return tabbed() ? marginTop() : _dropDown ? st::emojiFooterHeight : 0;
}
int TabbedSelector::marginBottom() const {
return st::emojiFooterHeight;
return _dropDown ? _roundRadius : st::emojiFooterHeight;
}
int TabbedSelector::scrollBottom() const {
return _dropDown ? 0 : marginBottom();
}
void TabbedSelector::refreshStickers() {

View file

@ -70,6 +70,7 @@ public:
Full,
EmojiOnly,
MediaEditor,
EmojiStatus,
};
enum class Action {
Update,
@ -110,9 +111,10 @@ public:
void beforeHiding();
void afterShown();
int marginTop() const;
int marginBottom() const;
int scrollTop() const;
[[nodiscard]] int marginTop() const;
[[nodiscard]] int marginBottom() const;
[[nodiscard]] int scrollTop() const;
[[nodiscard]] int scrollBottom() const;
bool preventAutoHide() const;
bool isSliding() const {
@ -128,6 +130,9 @@ public:
}
void showMenuWithType(SendMenu::Type type);
void setDropDown(bool dropDown) {
_dropDown = dropDown;
}
// Float player interface.
bool floatPlayerHandleWheelEvent(QEvent *e);
@ -193,6 +198,7 @@ private:
Tab createTab(SelectorTab type, int index);
void paintSlideFrame(Painter &p);
void paintBgRoundedPart(Painter &p);
void paintContent(Painter &p);
void checkRestrictedPeer();
@ -252,6 +258,7 @@ private:
const bool _hasGifsTab;
const bool _hasMasksTab;
const bool _tabbed;
bool _dropDown = false;
base::unique_qptr<Ui::PopupMenu> _menu;

View file

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text/text_block.h"
#include "ui/text/text_utilities.h"
#include "ui/special_buttons.h"
#include "ui/unread_badge.h"
@ -215,29 +216,25 @@ void Cover::setBadge(Badge badge, DocumentId emojiStatusId) {
_badge = badge;
_emojiStatusId = emojiStatusId;
_emojiStatus = nullptr;
_verifiedCheck.destroy();
_scamFakeBadge.destroy();
_badgeView.destroy();
switch (_badge) {
case Badge::Verified:
case Badge::Premium: {
const auto icon = (_badge == Badge::Verified)
? &st::infoVerifiedCheck
: &st::infoPremiumStar;
_verifiedCheck.create(this);
_verifiedCheck->show();
_verifiedCheck->resize(icon->size());
_badgeView.create(this);
_badgeView->show();
if (_emojiStatusId) {
auto &owner = _controller->session().data();
_emojiStatus = owner.customEmojiManager().create(
_emojiStatusId,
[raw = _verifiedCheck.data()]{ raw->update(); },
Data::CustomEmojiManager::SizeTag::Normal);
}
_verifiedCheck->paintRequest(
) | rpl::start_with_next([=, check = _verifiedCheck.data()] {
Painter p(check);
if (_emojiStatus) {
[raw = _badgeView.data()]{ raw->update(); },
Data::CustomEmojiManager::SizeTag::Large);
const auto size = Ui::Emoji::GetSizeLarge()
/ style::DevicePixelRatio();
const auto emoji = Ui::Text::AdjustCustomEmojiSize(size);
_badgeView->resize(emoji, emoji);
_badgeView->paintRequest(
) | rpl::start_with_next([=, check = _badgeView.data()]{
Painter p(check);
_emojiStatus->paint(
p,
0,
@ -246,24 +243,17 @@ void Cover::setBadge(Badge badge, DocumentId emojiStatusId) {
st::windowBgOver->c,
_controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer));
} else {
icon->paint(p, 0, 0, check->width());
}
}, _verifiedCheck->lifetime());
if (_badge == Badge::Premium) {
const auto userId = peerToUser(_peer->id).bare;
_verifiedCheck->setClickedCallback([=] {
if (_peer->isSelf()) {
showEmojiStatusSelector();
} else {
::Settings::ShowPremium(
_controller,
u"profile__%1"_q.arg(userId));
}
});
}, _badgeView->lifetime());
} else {
_verifiedCheck->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto icon = (_badge == Badge::Verified)
? &st::infoVerifiedCheck
: &st::infoPremiumStar;
_badgeView->resize(icon->size());
_badgeView->paintRequest(
) | rpl::start_with_next([=, check = _badgeView.data()]{
Painter p(check);
icon->paint(p, 0, 0, check->width());
}, _badgeView->lifetime());
}
} break;
case Badge::Scam:
@ -271,13 +261,13 @@ void Cover::setBadge(Badge badge, DocumentId emojiStatusId) {
const auto fake = (_badge == Badge::Fake);
const auto size = Ui::ScamBadgeSize(fake);
const auto skip = st::infoVerifiedCheckPosition.x();
_scamFakeBadge.create(this);
_scamFakeBadge->show();
_scamFakeBadge->resize(
_badgeView.create(this);
_badgeView->show();
_badgeView->resize(
size.width() + 2 * skip,
size.height() + 2 * skip);
_scamFakeBadge->paintRequest(
) | rpl::start_with_next([=, badge = _scamFakeBadge.data()]{
_badgeView->paintRequest(
) | rpl::start_with_next([=, badge = _badgeView.data()]{
Painter p(badge);
Ui::DrawScamBadge(
fake,
@ -285,28 +275,51 @@ void Cover::setBadge(Badge badge, DocumentId emojiStatusId) {
badge->rect().marginsRemoved({ skip, skip, skip, skip }),
badge->width(),
st::attentionButtonFg);
}, _scamFakeBadge->lifetime());
}, _badgeView->lifetime());
} break;
}
if (_badge == Badge::Premium) {
const auto userId = peerToUser(_peer->id).bare;
_badgeView->setClickedCallback([=] {
if (_peer->isSelf()) {
showEmojiStatusSelector();
} else {
::Settings::ShowPremium(
_controller,
u"profile__%1"_q.arg(userId));
}
});
} else {
_badgeView->setAttribute(Qt::WA_TransparentForMouseEvents);
}
refreshNameGeometry(width());
}
void Cover::showEmojiStatusSelector() {
Expects(_verifiedCheck != nullptr);
Expects(_badgeView != nullptr);
if (!_emojiStatusPanel) {
createEmojiStatusSelector();
}
const auto parent = _emojiStatusPanel->parentWidget();
const auto global = _verifiedCheck->mapToGlobal({ 0, 0 });
const auto global = _badgeView->mapToGlobal({ 0, 0 });
const auto local = parent->mapFromGlobal(global);
_emojiStatusPanel->moveBottomRight(
local.y(),
local.x() + _verifiedCheck->width() * 3);
_emojiStatusPanel->moveTopRight(
local.y() + _badgeView->height(),
local.x() + _badgeView->width() * 3);
_emojiStatusPanel->toggleAnimated();
}
void Cover::createEmojiStatusSelector() {
const auto set = [=](DocumentId id) {
_controller->session().user()->setEmojiStatus(id);
_controller->session().api().request(MTPaccount_UpdateEmojiStatus(
id ? MTP_emojiStatus(MTP_long(id)) : MTP_emojiStatusEmpty()
)).send();
_emojiStatusPanel->hideAnimated();
};
const auto container = _controller->window().widget()->bodyWidget();
using Selector = ChatHelpers::TabbedSelector;
_emojiStatusPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
@ -316,20 +329,21 @@ void Cover::createEmojiStatusSelector() {
nullptr,
_controller,
Window::GifPauseReason::Layer,
ChatHelpers::TabbedSelector::Mode::EmojiOnly));
ChatHelpers::TabbedSelector::Mode::EmojiStatus));
_emojiStatusPanel->setDropDown(true);
_emojiStatusPanel->setDesiredHeightValues(
1.,
st::emojiPanMinHeight / 2,
st::emojiPanMinHeight);
_emojiStatusPanel->hide();
_emojiStatusPanel->selector()->setAllowEmojiWithoutPremium(false);
_emojiStatusPanel->selector()->emojiChosen(
) | rpl::start_with_next([=] {
set(0);
}, _emojiStatusPanel->lifetime());
_emojiStatusPanel->selector()->customEmojiChosen(
) | rpl::start_with_next([=](Selector::FileChosen data) {
_controller->session().user()->setEmojiStatus(data.document->id);
_controller->session().api().request(MTPaccount_UpdateEmojiStatus(
MTP_emojiStatus(MTP_long(data.document->id))
)).send();
_emojiStatusPanel->hideAnimated();
set(data.document->id);
}, _emojiStatusPanel->lifetime());
_emojiStatusPanel->selector()->showPromoForPremiumEmoji();
}
@ -392,31 +406,22 @@ void Cover::refreshNameGeometry(int newWidth) {
auto nameWidth = newWidth
- nameLeft
- st::infoProfileNameRight;
if (_verifiedCheck) {
nameWidth -= st::infoVerifiedCheckPosition.x()
+ _verifiedCheck->width();
} else if (_scamFakeBadge) {
nameWidth -= st::infoVerifiedCheckPosition.x()
+ _scamFakeBadge->width();
if (_badgeView) {
nameWidth -= st::infoVerifiedCheckPosition.x() + _badgeView->width();
}
_name->resizeToNaturalWidth(nameWidth);
_name->moveToLeft(nameLeft, nameTop, newWidth);
if (_verifiedCheck) {
const auto checkLeft = nameLeft
+ _name->width()
+ st::infoVerifiedCheckPosition.x();
const auto checkTop = nameTop
+ st::infoVerifiedCheckPosition.y();
_verifiedCheck->moveToLeft(checkLeft, checkTop, newWidth);
} else if (_scamFakeBadge) {
const auto skip = st::infoVerifiedCheckPosition.x();
const auto badgeLeft = nameLeft
+ _name->width()
+ st::infoVerifiedCheckPosition.x()
- skip;
if (_badgeView) {
const auto star = !_emojiStatus
&& (_badge == Badge::Premium || _badge == Badge::Verified);
const auto fake = !_emojiStatus && !star;
const auto skip = fake ? 0 : st::infoVerifiedCheckPosition.x();
const auto badgeLeft = nameLeft + _name->width() + skip;
const auto badgeTop = nameTop
+ (_name->height() - _scamFakeBadge->height()) / 2;
_scamFakeBadge->moveToLeft(badgeLeft, badgeTop, newWidth);
+ (star
? st::infoVerifiedCheckPosition.y()
: (_name->height() - _badgeView->height()) / 2);
_badgeView->moveToLeft(badgeLeft, badgeTop, newWidth);
}
}

View file

@ -82,8 +82,7 @@ private:
object_ptr<Ui::UserpicButton> _userpic;
object_ptr<Ui::FlatLabel> _name = { nullptr };
object_ptr<Ui::AbstractButton> _verifiedCheck = { nullptr };
object_ptr<Ui::RpWidget> _scamFakeBadge = { nullptr };
object_ptr<Ui::AbstractButton> _badgeView = { nullptr };
object_ptr<Ui::FlatLabel> _status = { nullptr };
//object_ptr<CoverDropArea> _dropArea = { nullptr };
base::Timer _refreshStatusTimer;