| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- // This file is part of Desktop App Toolkit,
- // a set of libraries for developing nice desktop applications.
- //
- // For license and copyright information please follow this link:
- // https://github.com/desktop-app/legal/blob/master/LEGAL
- //
- #include "ui/widgets/inner_dropdown.h"
- #include "ui/widgets/scroll_area.h"
- #include "ui/widgets/shadow.h"
- #include "ui/effects/panel_animation.h"
- #include "ui/image/image_prepare.h"
- #include "ui/qt_weak_factory.h"
- #include "ui/ui_utility.h"
- namespace Ui {
- InnerDropdown::InnerDropdown(
- QWidget *parent,
- const style::InnerDropdown &st)
- : RpWidget(parent)
- , _st(st)
- , _roundRect(ImageRoundRadius::Small, _st.bg)
- , _hideTimer([=] { hideAnimated(); })
- , _scroll(this, _st.scroll) {
- _scroll->scrolls(
- ) | rpl::start_with_next([=] {
- scrolled();
- }, lifetime());
- hide();
- shownValue(
- ) | rpl::filter([](bool shown) {
- return shown;
- }) | rpl::take(1) | rpl::map([=] {
- // We can't invoke this before the window is created.
- // So instead we start handling them on the first show().
- return macWindowDeactivateEvents();
- }) | rpl::flatten_latest(
- ) | rpl::filter([=] {
- return !isHidden();
- }) | rpl::start_with_next([=] {
- leaveEvent(nullptr);
- }, lifetime());
- }
- QPointer<RpWidget> InnerDropdown::doSetOwnedWidget(
- object_ptr<RpWidget> widget) {
- auto result = QPointer<RpWidget>(widget);
- widget->heightValue(
- ) | rpl::skip(1) | rpl::start_with_next([=] {
- resizeToContent();
- }, widget->lifetime());
- auto container = _scroll->setOwnedWidget(
- object_ptr<Container>(
- _scroll,
- std::move(widget),
- _st));
- container->resizeToWidth(_scroll->width());
- container->moveToLeft(0, 0);
- container->show();
- result->show();
- return result;
- }
- void InnerDropdown::setMaxHeight(int newMaxHeight) {
- _maxHeight = newMaxHeight;
- resizeToContent();
- }
- void InnerDropdown::resizeToContent() {
- auto newWidth = _st.padding.left() + _st.scrollMargin.left() + _st.scrollMargin.right() + _st.padding.right();
- auto newHeight = _st.padding.top() + _st.scrollMargin.top() + _st.scrollMargin.bottom() + _st.padding.bottom();
- if (auto widget = static_cast<Container*>(_scroll->widget())) {
- widget->resizeToContent();
- newWidth += widget->width();
- newHeight += widget->height();
- }
- if (_maxHeight > 0) {
- accumulate_min(newHeight, _maxHeight);
- }
- if (newWidth != width() || newHeight != height()) {
- resize(newWidth, newHeight);
- update();
- finishAnimating();
- }
- }
- void InnerDropdown::resizeEvent(QResizeEvent *e) {
- _scroll->setGeometry(rect().marginsRemoved(_st.padding).marginsRemoved(_st.scrollMargin));
- if (auto widget = static_cast<TWidget*>(_scroll->widget())) {
- widget->resizeToWidth(_scroll->width());
- scrolled();
- }
- }
- void InnerDropdown::scrolled() {
- if (auto widget = static_cast<TWidget*>(_scroll->widget())) {
- int visibleTop = _scroll->scrollTop();
- int visibleBottom = visibleTop + _scroll->height();
- widget->setVisibleTopBottom(visibleTop, visibleBottom);
- }
- }
- void InnerDropdown::paintEvent(QPaintEvent *e) {
- QPainter p(this);
- if (_a_show.animating()) {
- if (auto opacity = _a_opacity.value(_hiding ? 0. : 1.)) {
- // _a_opacity.current(ms)->opacityAnimationCallback()->_showAnimation.reset()
- if (_showAnimation) {
- _showAnimation->paintFrame(p, 0, 0, width(), _a_show.value(1.), opacity);
- }
- }
- } else if (_a_opacity.animating()) {
- p.setOpacity(_a_opacity.value(0.));
- p.drawPixmap(0, 0, _cache);
- } else if (_hiding || isHidden()) {
- hideFinished();
- } else if (_showAnimation) {
- _showAnimation->paintFrame(p, 0, 0, width(), 1., 1.);
- _showAnimation.reset();
- showChildren();
- } else {
- if (!_cache.isNull()) _cache = QPixmap();
- const auto inner = rect().marginsRemoved(_st.padding);
- Shadow::paint(p, inner, width(), _st.shadow);
- _roundRect.paint(p, inner);
- }
- }
- void InnerDropdown::enterEventHook(QEnterEvent *e) {
- if (_autoHiding) {
- showAnimated(_origin);
- }
- return RpWidget::enterEventHook(e);
- }
- void InnerDropdown::leaveEventHook(QEvent *e) {
- if (_autoHiding) {
- if (_a_show.animating() || _a_opacity.animating()) {
- hideAnimated();
- } else {
- _hideTimer.callOnce(300);
- }
- }
- return RpWidget::leaveEventHook(e);
- }
- void InnerDropdown::otherEnter() {
- if (_autoHiding) {
- showAnimated(_origin);
- }
- }
- void InnerDropdown::otherLeave() {
- if (_autoHiding) {
- if (_a_show.animating() || _a_opacity.animating()) {
- hideAnimated();
- } else {
- _hideTimer.callOnce(0);
- }
- }
- }
- void InnerDropdown::setOrigin(PanelAnimation::Origin origin) {
- _origin = origin;
- }
- void InnerDropdown::showAnimated(PanelAnimation::Origin origin) {
- setOrigin(origin);
- showAnimated();
- }
- void InnerDropdown::showAnimated() {
- _hideTimer.cancel();
- showStarted();
- }
- void InnerDropdown::hideAnimated(HideOption option) {
- if (isHidden()) return;
- if (option == HideOption::IgnoreShow) {
- _ignoreShowEvents = true;
- }
- if (_hiding) return;
- _hideTimer.cancel();
- startOpacityAnimation(true);
- }
- void InnerDropdown::finishAnimating() {
- if (_a_show.animating()) {
- _a_show.stop();
- showAnimationCallback();
- }
- if (_showAnimation) {
- _showAnimation.reset();
- showChildren();
- }
- if (_a_opacity.animating()) {
- _a_opacity.stop();
- opacityAnimationCallback();
- }
- }
- void InnerDropdown::showFast() {
- _hideTimer.cancel();
- finishAnimating();
- if (isHidden()) {
- showChildren();
- show();
- }
- _hiding = false;
- }
- void InnerDropdown::hideFast() {
- if (isHidden()) return;
- _hideTimer.cancel();
- finishAnimating();
- _hiding = false;
- hideFinished();
- }
- void InnerDropdown::hideFinished() {
- _a_show.stop();
- _showAnimation.reset();
- _cache = QPixmap();
- _ignoreShowEvents = false;
- if (!isHidden()) {
- const auto weak = Ui::MakeWeak(this);
- if (const auto onstack = _hiddenCallback) {
- onstack();
- }
- if (weak) {
- hide();
- }
- }
- }
- void InnerDropdown::prepareCache() {
- if (_a_opacity.animating()) return;
- const auto animating = _a_show.animating();
- auto showAnimation = base::take(_a_show);
- auto showAnimationData = base::take(_showAnimation);
- showChildren();
- _cache = GrabWidget(this);
- if (animating) {
- hideChildren();
- }
- _showAnimation = base::take(showAnimationData);
- _a_show = base::take(showAnimation);
- }
- void InnerDropdown::startOpacityAnimation(bool hiding) {
- const auto weak = Ui::MakeWeak(this);
- if (hiding) {
- if (const auto onstack = _hideStartCallback) {
- onstack();
- }
- } else if (const auto onstack = _showStartCallback) {
- onstack();
- }
- if (!weak) {
- return;
- }
- _hiding = false;
- prepareCache();
- _hiding = hiding;
- hideChildren();
- _a_opacity.start(
- [=] { opacityAnimationCallback(); },
- _hiding ? 1. : 0.,
- _hiding ? 0. : 1.,
- _st.duration);
- }
- void InnerDropdown::showStarted() {
- if (_ignoreShowEvents) return;
- if (isHidden()) {
- show();
- startShowAnimation();
- return;
- } else if (!_hiding) {
- return;
- }
- startOpacityAnimation(false);
- }
- void InnerDropdown::startShowAnimation() {
- if (_showStartCallback) {
- _showStartCallback();
- }
- if (!_a_show.animating()) {
- auto opacityAnimation = base::take(_a_opacity);
- showChildren();
- auto cache = grabForPanelAnimation();
- _a_opacity = base::take(opacityAnimation);
- const auto pixelRatio = style::DevicePixelRatio();
- _showAnimation = std::make_unique<PanelAnimation>(_st.animation, _origin);
- auto inner = rect().marginsRemoved(_st.padding);
- _showAnimation->setFinalImage(std::move(cache), QRect(inner.topLeft() * pixelRatio, inner.size() * pixelRatio));
- _showAnimation->setCornerMasks(
- Images::CornersMask(ImageRoundRadius::Small));
- _showAnimation->start();
- }
- hideChildren();
- _a_show.start([this] { showAnimationCallback(); }, 0., 1., _st.showDuration);
- }
- QImage InnerDropdown::grabForPanelAnimation() {
- SendPendingMoveResizeEvents(this);
- const auto pixelRatio = style::DevicePixelRatio();
- auto result = QImage(size() * pixelRatio, QImage::Format_ARGB32_Premultiplied);
- result.setDevicePixelRatio(pixelRatio);
- result.fill(Qt::transparent);
- {
- QPainter p(&result);
- _roundRect.paint(p, rect().marginsRemoved(_st.padding));
- for (const auto child : children()) {
- if (const auto widget = qobject_cast<QWidget*>(child)) {
- RenderWidget(p, widget, widget->pos());
- }
- }
- }
- return result;
- }
- void InnerDropdown::opacityAnimationCallback() {
- update();
- if (!_a_opacity.animating()) {
- if (_hiding) {
- _hiding = false;
- hideFinished();
- } else if (!_a_show.animating()) {
- showChildren();
- }
- }
- }
- void InnerDropdown::showAnimationCallback() {
- update();
- }
- bool InnerDropdown::eventFilter(QObject *obj, QEvent *e) {
- if (e->type() == QEvent::Enter) {
- otherEnter();
- } else if (e->type() == QEvent::Leave) {
- otherLeave();
- } else if (e->type() == QEvent::MouseButtonRelease && static_cast<QMouseEvent*>(e)->button() == Qt::LeftButton) {
- if (isHidden() || _hiding) {
- otherEnter();
- } else {
- otherLeave();
- }
- }
- return false;
- }
- int InnerDropdown::resizeGetHeight(int newWidth) {
- auto newHeight = _st.padding.top() + _st.scrollMargin.top() + _st.scrollMargin.bottom() + _st.padding.bottom();
- if (auto widget = static_cast<TWidget*>(_scroll->widget())) {
- auto containerWidth = newWidth - _st.padding.left() - _st.padding.right() - _st.scrollMargin.left() - _st.scrollMargin.right();
- widget->resizeToWidth(containerWidth);
- newHeight += widget->height();
- }
- if (_maxHeight > 0) {
- accumulate_min(newHeight, _maxHeight);
- }
- return newHeight;
- }
- InnerDropdown::Container::Container(QWidget *parent, object_ptr<TWidget> child, const style::InnerDropdown &st) : TWidget(parent)
- , _child(std::move(child))
- , _st(st) {
- _child->setParent(this);
- _child->moveToLeft(_st.scrollPadding.left(), _st.scrollPadding.top());
- }
- void InnerDropdown::Container::visibleTopBottomUpdated(
- int visibleTop,
- int visibleBottom) {
- setChildVisibleTopBottom(_child, visibleTop, visibleBottom);
- }
- void InnerDropdown::Container::resizeToContent() {
- auto newWidth = _st.scrollPadding.left() + _st.scrollPadding.right();
- auto newHeight = _st.scrollPadding.top() + _st.scrollPadding.bottom();
- if (auto child = static_cast<TWidget*>(children().front())) {
- newWidth += child->width();
- newHeight += child->height();
- }
- if (newWidth != width() || newHeight != height()) {
- resize(newWidth, newHeight);
- }
- }
- int InnerDropdown::Container::resizeGetHeight(int newWidth) {
- auto innerWidth = newWidth - _st.scrollPadding.left() - _st.scrollPadding.right();
- auto result = _st.scrollPadding.top() + _st.scrollPadding.bottom();
- _child->resizeToWidth(innerWidth);
- _child->moveToLeft(_st.scrollPadding.left(), _st.scrollPadding.top());
- result += _child->height();
- return result;
- }
- } // namespace Ui
|