/* 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 "ui/controls/labeled_emoji_tabs.h" #include "base/object_ptr.h" #include "ui/abstract_button.h" #include "ui/effects/ripple_animation.h" #include "ui/painter.h" #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" #include "styles/style_basic.h" #include "styles/style_boxes.h" #include #include #include #include namespace Ui { namespace { [[nodiscard]] QColor ColorWithAlpha( const style::color &color, float64 alpha) { auto result = color->c; result.setAlphaF(result.alphaF() * alpha); return result; } [[nodiscard]] QColor ActiveBackgroundColor(const style::color &color) { return ColorWithAlpha(color, st::aiComposeButtonBgActiveOpacity); } [[nodiscard]] QColor RippleColor( const style::RippleAnimation &ripple, float64 opacity) { return ColorWithAlpha(ripple.color, opacity); } [[nodiscard]] qreal TabsRadius() { return st::aiComposeStyleTabsRadius; } } // namespace class LabeledEmojiTabs::Button final : public RippleButton { public: Button( QWidget *parent, LabeledEmojiTab descriptor, Text::CustomEmojiFactory factory); void setSelected(bool selected); void setExtraPadding(int extra); [[nodiscard]] const QString &id() const; protected: void paintEvent(QPaintEvent *e) override; [[nodiscard]] QImage prepareRippleMask() const override; private: const LabeledEmojiTab _descriptor; std::unique_ptr _custom; bool _selected = false; int _extraPadding = 0; }; class LabeledEmojiScrollTabs::DragScroll final : public QObject { public: DragScroll( not_null parent, not_null scroll, Fn beforeScroll); void add(not_null button); protected: bool eventFilter(QObject *watched, QEvent *event) override; private: const not_null _scroll; const Fn _beforeScroll; QPointer _pressed; QPoint _pressGlobal; int _scrollStart = 0; bool _dragging = false; }; LabeledEmojiTabs::Button::Button( QWidget *parent, LabeledEmojiTab descriptor, Text::CustomEmojiFactory factory) : RippleButton(parent, st::aiComposeButtonRippleInactive) , _descriptor(std::move(descriptor)) , _custom(!_descriptor.customEmojiData.isEmpty() && factory ? factory( _descriptor.customEmojiData, { .repaint = [this] { update(); } }) : nullptr) { setCursor(style::cur_pointer); setNaturalWidth([&] { const auto padding = st::aiComposeStyleButtonPadding; const auto labelWidth = st::aiComposeStyleLabelFont->width( _descriptor.label); const auto emojiWidth = (_custom || _descriptor.emoji) ? (Emoji::GetSizeLarge() / style::DevicePixelRatio()) : 0; return padding.left() + std::max(labelWidth, emojiWidth) + padding.right(); }()); setExtraPadding(0); } void LabeledEmojiTabs::Button::setSelected(bool selected) { if (_selected == selected) { return; } _selected = selected; update(); } void LabeledEmojiTabs::Button::setExtraPadding(int extra) { _extraPadding = extra; resize(naturalWidth() + 2 * extra, height()); } const QString &LabeledEmojiTabs::Button::id() const { return _descriptor.id; } void LabeledEmojiTabs::Button::paintEvent(QPaintEvent *e) { Painter p(this); PainterHighQualityEnabler hq(p); const auto radius = TabsRadius(); if (_selected) { p.setPen(Qt::NoPen); p.setBrush(ActiveBackgroundColor(st::aiComposeStyleButtonBgActive)); p.drawRoundedRect(rect(), radius, radius); } const auto ripple = RippleColor( _selected ? st::aiComposeButtonRippleActive : st::aiComposeButtonRippleInactive, _selected ? st::aiComposeButtonRippleActiveOpacity : st::aiComposeButtonRippleInactiveOpacity); paintRipple(p, 0, 0, &ripple); if (_custom) { const auto size = Emoji::GetSizeLarge() / style::DevicePixelRatio(); const auto adjusted = Text::AdjustCustomEmojiSize(size); const auto skip = (size - adjusted) / 2; const auto left = (width() - size) / 2; _custom->paint(p, { .textColor = (_selected ? st::aiComposeStyleLabelFgActive : st::aiComposeStyleLabelFg)->c, .now = crl::now(), .position = { left + skip, st::aiComposeStyleEmojiTop + skip, }, }); } else if (_descriptor.emoji) { const auto size = Emoji::GetSizeLarge() / style::DevicePixelRatio(); const auto left = (width() - size) / 2; Emoji::Draw( p, _descriptor.emoji, Emoji::GetSizeLarge(), left, st::aiComposeStyleEmojiTop); } p.setPen(_selected ? st::aiComposeStyleLabelFgActive : st::aiComposeStyleLabelFg); p.setFont(st::aiComposeStyleLabelFont); p.drawText( QRect( 0, st::aiComposeStyleLabelTop, width(), height() - st::aiComposeStyleLabelTop), Qt::AlignHCenter | Qt::AlignTop, _descriptor.label); } QImage LabeledEmojiTabs::Button::prepareRippleMask() const { return RippleAnimation::MaskByDrawer(size(), false, [&](QPainter &p) { p.setPen(Qt::NoPen); p.setBrush(Qt::white); const auto radius = TabsRadius(); p.drawRoundedRect(rect(), radius, radius); }); } LabeledEmojiScrollTabs::DragScroll::DragScroll( not_null parent, not_null scroll, Fn beforeScroll) : QObject(parent) , _scroll(scroll) , _beforeScroll(std::move(beforeScroll)) { } void LabeledEmojiScrollTabs::DragScroll::add( not_null button) { button->installEventFilter(this); } bool LabeledEmojiScrollTabs::DragScroll::eventFilter( QObject *watched, QEvent *event) { const auto button = dynamic_cast(watched); if (!button) { return QObject::eventFilter(watched, event); } switch (event->type()) { case QEvent::MouseButtonPress: { const auto mouse = static_cast(event); if (mouse->button() != Qt::LeftButton) { break; } _pressed = button; _pressGlobal = mouse->globalPos(); _scrollStart = _scroll->scrollLeft(); _dragging = false; break; } case QEvent::MouseMove: { const auto mouse = static_cast(event); if (_pressed != button || !(mouse->buttons() & Qt::LeftButton)) { break; } const auto delta = mouse->globalPos() - _pressGlobal; if (!_dragging) { if (!_scroll->scrollLeftMax()) { break; } if (std::abs(delta.x()) < QApplication::startDragDistance()) { break; } if (std::abs(delta.x()) <= std::abs(delta.y())) { break; } _dragging = true; button->setSynteticOver(false); } if (_beforeScroll) { _beforeScroll(); } button->setSynteticOver(false); _scroll->scrollToX(_scrollStart - delta.x()); return true; } case QEvent::MouseButtonRelease: { const auto mouse = static_cast(event); if (_pressed != button || mouse->button() != Qt::LeftButton) { break; } const auto dragging = std::exchange(_dragging, false); _pressed = nullptr; if (dragging) { button->setSynteticOver(false); } break; } case QEvent::Hide: if (_pressed == button) { _pressed = nullptr; _dragging = false; } break; default: break; } return QObject::eventFilter(watched, event); } LabeledEmojiTabs::LabeledEmojiTabs( QWidget *parent, std::vector descriptors, Text::CustomEmojiFactory factory) : RpWidget(parent) { _buttons.reserve(descriptors.size()); for (auto &descriptor : descriptors) { const auto button = CreateChild