/* 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 "settings/settings_common.h" #include "base/timer.h" #include "lottie/lottie_icon.h" #include "ui/effects/animations.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_top_bar.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/widgets/buttons.h" #include "ui/widgets/continuous_sliders.h" #include "ui/widgets/elastic_scroll.h" #include "ui/widgets/labels.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/vertical_layout.h" #include "styles/style_layers.h" #include "styles/style_menu_icons.h" #include "styles/style_settings.h" #include "styles/style_widgets.h" #include namespace Settings { namespace { [[nodiscard]] HighlightArgs MaybeFillFromRipple( not_null target, HighlightArgs args) { if (!args.rippleShape) { return args; } const auto widget = target.get(); if (const auto icon = dynamic_cast(widget)) { const auto &st = icon->st(); args.shape = HighlightShape::Ellipse; args.margin = QMargins( st.rippleAreaPosition.x(), st.rippleAreaPosition.y(), st.width - st.rippleAreaPosition.x() - st.rippleAreaSize, st.height - st.rippleAreaPosition.y() - st.rippleAreaSize); } else if (const auto round = dynamic_cast(widget)) { const auto &st = round->st(); args.shape = HighlightShape::Rect; args.radius = st.radius ? st.radius : st::buttonRadius; } return args; } class HighlightOverlay final : public Ui::RpWidget { public: HighlightOverlay(not_null target, HighlightArgs &&args); protected: bool eventFilter(QObject *o, QEvent *e) override; private: enum class Phase { Wait, FadeIn, Shown, FadeOut, }; void updateGeometryFromTarget(); void updateZOrder(); void startFadeIn(); void startFadeOut(); void nextPhase(); void nextPhaseIn(crl::time delay); void finish(); const QPointer _target; const HighlightArgs _args; const style::color &_color; Ui::Animations::Simple _animation; base::Timer _phaseTimer; Phase _phase = Phase::Wait; bool _finishing = false; }; HighlightOverlay::HighlightOverlay( not_null target, HighlightArgs &&args) : RpWidget(target->parentWidget()) , _target(target.get()) , _args(MaybeFillFromRipple(target, std::move(args))) , _color(_args.color ? *_args.color : st::windowBgActive) , _phaseTimer([=] { nextPhase(); }) { setAttribute(Qt::WA_TransparentForMouseEvents); target->installEventFilter(this); QObject::connect( target.get(), &QObject::destroyed, this, [this] { finish(); }); updateGeometryFromTarget(); updateZOrder(); paintRequest() | rpl::on_next([=](QRect clip) { auto p = QPainter(this); const auto progress = _animation.value( (_phase == Phase::Wait || _phase == Phase::FadeOut) ? 0. : 1.); const auto alpha = _args.opacity * progress; auto color = _color->c; color.setAlphaF(color.alphaF() * alpha); p.setPen(Qt::NoPen); p.setBrush(color); const auto r = rect(); switch (_args.shape) { case HighlightShape::Rect: if (_args.radius > 0) { PainterHighQualityEnabler hq(p); p.drawRoundedRect(r, _args.radius, _args.radius); } else { p.drawRect(r); } break; case HighlightShape::Ellipse: PainterHighQualityEnabler hq(p); p.drawEllipse(r); break; } }, lifetime()); hide(); nextPhaseIn(_args.showDelay); } bool HighlightOverlay::eventFilter(QObject *o, QEvent *e) { if (o != _target.data()) { return false; } switch (e->type()) { case QEvent::Move: case QEvent::Resize: updateGeometryFromTarget(); break; case QEvent::ZOrderChange: updateZOrder(); break; case QEvent::Show: case QEvent::Hide: setVisible(!_target->isHidden()); break; default: break; } return false; } void HighlightOverlay::updateGeometryFromTarget() { if (_target) { setGeometry(_target->geometry().marginsRemoved(_args.margin)); } } void HighlightOverlay::updateZOrder() { if (!_target) { return; } if (_args.below) { stackUnder(_target); } else { const auto parent = _target->parentWidget(); const auto siblings = parent ? parent->children() : QObjectList(); const auto it = ranges::find(siblings, _target.data()); const auto next = (it != siblings.end()) ? (it + 1) : it; if (next != siblings.end()) { if (const auto widget = qobject_cast(*next)) { stackUnder(widget); return; } } raise(); } } void HighlightOverlay::startFadeIn() { show(); _phase = Phase::FadeIn; _animation.start([=] { update(); if (!_animation.animating()) { _phase = Phase::Shown; nextPhaseIn(_args.shownDuration); } }, 0., 1., _args.showDuration, anim::easeOutCubic); } void HighlightOverlay::startFadeOut() { _phase = Phase::FadeOut; _animation.start([=] { update(); if (!_animation.animating()) { finish(); } }, 1., 0., _args.hideDuration, anim::easeInCubic); } void HighlightOverlay::nextPhase() { switch (_phase) { case Phase::Wait: startFadeIn(); break; case Phase::Shown: startFadeOut(); break; default: Unexpected("Next phase called during Fade phase."); } } void HighlightOverlay::nextPhaseIn(crl::time delay) { if (!delay) { _phaseTimer.cancel(); nextPhase(); } else { _phaseTimer.callOnce(delay); } } void HighlightOverlay::finish() { if (_finishing) { return; } _finishing = true; _phaseTimer.cancel(); _animation.stop(); deleteLater(); } } // namespace void HighlightWidget(QWidget *target, HighlightArgs &&args) { if (!target) { return; } if (args.scroll) { ScrollToWidget(target); } new HighlightOverlay(target, std::move(args)); } void ScrollToWidget(not_null target) { const auto scrollIn = [&](auto &&scroll) { if (const auto inner = scroll->widget()) { const auto globalPosition = target->mapToGlobal(QPoint(0, 0)); const auto localPosition = inner->mapFromGlobal(globalPosition); const auto localTop = localPosition.y(); const auto targetHeight = target->height(); const auto scrollHeight = scroll->height(); const auto centered = localTop - (scrollHeight - targetHeight) / 2; const auto top = std::clamp(centered, 0, scroll->scrollTopMax()); scroll->scrollToY(top); } }; for (auto parent = target->parentWidget() ; parent ; parent = parent->parentWidget()) { if (const auto scroll = dynamic_cast(parent)) { scrollIn(scroll); return; } if (const auto scroll = dynamic_cast(parent)) { scrollIn(scroll); return; } } } HighlightArgs SubsectionTitleHighlight() { const auto radius = st::roundRadiusSmall; return { .margin = { -radius, 0, -radius, 0 }, .radius = radius }; } void AbstractSection::build( not_null container, SectionBuilder builder) { builder( static_cast(container->add( object_ptr( container, object_ptr(container)))->entity()), _controller, showOtherMethod(), _showFinished.events()); } Icon::Icon(IconDescriptor descriptor) : _icon(descriptor.icon) { const auto background = [&]() -> const style::color* { if (descriptor.type == IconType::Simple) { return nullptr; } return descriptor.background; }(); if (background) { const auto radius = (descriptor.type == IconType::Rounded) ? st::settingsIconRadius : (std::min(_icon->width(), _icon->height()) / 2); _background.emplace(radius, *background); } else if (const auto brush = descriptor.backgroundBrush) { const auto radius = (descriptor.type == IconType::Rounded) ? st::settingsIconRadius : (std::min(_icon->width(), _icon->height()) / 2); _backgroundBrush.emplace(radius, std::move(*brush)); } } void Icon::paint(QPainter &p, QPoint position) const { paint(p, position.x(), position.y()); } void Icon::paint(QPainter &p, int x, int y) const { if (_background) { _background->paint(p, { { x, y }, _icon->size() }); } else if (_backgroundBrush) { PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(_backgroundBrush->second); p.drawRoundedRect( QRect(QPoint(x, y), _icon->size()), _backgroundBrush->first, _backgroundBrush->first); } _icon->paint(p, { x, y }, 2 * x + _icon->width()); } int Icon::width() const { return _icon->width(); } int Icon::height() const { return _icon->height(); } QSize Icon::size() const { return _icon->size(); } void AddButtonIcon( not_null button, const style::SettingsButton &st, IconDescriptor &&descriptor) { Expects(descriptor.icon != nullptr); struct IconWidget { IconWidget(QWidget *parent, IconDescriptor &&descriptor) : widget(parent) , icon(std::move(descriptor)) { } Ui::RpWidget widget; Icon icon; }; const auto icon = button->lifetime().make_state( button, std::move(descriptor)); icon->widget.setAttribute(Qt::WA_TransparentForMouseEvents); icon->widget.resize(icon->icon.size()); icon->widget.show(); button->sizeValue( ) | rpl::on_next([=, left = st.iconLeft](QSize size) { icon->widget.moveToLeft( left, (size.height() - icon->widget.height()) / 2, size.width()); }, icon->widget.lifetime()); icon->widget.paintRequest( ) | rpl::on_next([=] { auto p = QPainter(&icon->widget); icon->icon.paint(p, 0, 0); }, icon->widget.lifetime()); } object_ptr