// 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/layers/box_layer_widget.h" #include "ui/effects/radial_animation.h" #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/labels.h" #include "ui/widgets/shadow.h" #include "ui/wrap/fade_wrap.h" #include "ui/text/text_utilities.h" #include "ui/image/image_prepare.h" #include "ui/painter.h" #include "base/timer.h" #include "styles/style_layers.h" #include "styles/palette.h" namespace Ui { struct BoxLayerWidget::LoadingProgress { LoadingProgress( Fn &&callback, const style::InfiniteRadialAnimation &st); InfiniteRadialAnimation animation; base::Timer removeTimer; }; BoxLayerWidget::LoadingProgress::LoadingProgress( Fn &&callback, const style::InfiniteRadialAnimation &st) : animation(std::move(callback), st) { } BoxLayerWidget::BoxLayerWidget( not_null layer, object_ptr content) : LayerWidget(layer) , _layer(layer) , _content(std::move(content)) , _roundRect(ImageRoundRadius::Small, st::boxBg) { _content->setParent(this); _content->setDelegate(this); _additionalTitle.changes( ) | rpl::start_with_next([=] { updateSize(); update(); }, lifetime()); } BoxLayerWidget::~BoxLayerWidget() = default; void BoxLayerWidget::setLayerType(bool layerType) { if (_layerType == layerType) { return; } _layerType = layerType; updateTitlePosition(); if (_maxContentHeight) { setDimensions(width(), _maxContentHeight); } } int BoxLayerWidget::titleHeight() const { return _layerType ? st::boxLayerTitleHeight : st::boxTitleHeight; } int BoxLayerWidget::buttonsHeight() const { const auto padding = _layerType ? st::boxLayerButtonPadding : st::boxButtonPadding; return padding.top() + st::defaultBoxButton.height + padding.bottom(); } int BoxLayerWidget::buttonsTop() const { const auto padding = _layerType ? st::boxLayerButtonPadding : st::boxButtonPadding; return height() - padding.bottom() - st::defaultBoxButton.height; } QRect BoxLayerWidget::loadingRect() const { const auto padding = _layerType ? st::boxLayerButtonPadding : st::boxButtonPadding; const auto size = st::boxLoadingSize; const auto skipx = _layerType ? st::boxLayerTitlePosition.x() : st::boxTitlePosition.x(); const auto skipy = (st::defaultBoxButton.height - size) / 2; return QRect( skipx, height() - padding.bottom() - skipy - size, size, size); } void BoxLayerWidget::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); auto paintTopRounded = clip.intersects(QRect(0, 0, width(), st::boxRadius)); auto paintBottomRounded = clip.intersects(QRect(0, height() - st::boxRadius, width(), st::boxRadius)); if (paintTopRounded || paintBottomRounded) { auto parts = RectPart::None | 0; if (paintTopRounded) parts |= RectPart::FullTop; if (paintBottomRounded) parts |= RectPart::FullBottom; _roundRect.paint(p, rect(), parts); } auto other = e->region().intersected(QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius)); if (!other.isEmpty()) { for (auto rect : other.rects()) { p.fillRect(rect, st::boxBg); } } if (!_additionalTitle.current().isEmpty() && clip.intersects(QRect(0, 0, width(), titleHeight()))) { paintAdditionalTitle(p); } if (_loadingProgress) { const auto rect = loadingRect(); _loadingProgress->animation.draw( p, rect.topLeft(), rect.size(), width()); } } void BoxLayerWidget::paintAdditionalTitle(Painter &p) { p.setFont(st::boxLayerTitleAdditionalFont); p.setPen(st::boxTitleAdditionalFg); p.drawTextLeft(_titleLeft + (_title ? _title->width() : 0) + st::boxLayerTitleAdditionalSkip, _titleTop + st::boxTitleFont->ascent - st::boxLayerTitleAdditionalFont->ascent, width(), _additionalTitle.current()); } void BoxLayerWidget::parentResized() { auto newHeight = countRealHeight(); auto parentSize = parentWidget()->size(); setGeometry((parentSize.width() - width()) / 2, (parentSize.height() - newHeight) / 2, width(), newHeight); update(); } void BoxLayerWidget::setTitle(rpl::producer title) { const auto wasTitle = hasTitle(); if (title) { _title.create(this, std::move(title), st::boxTitle); _title->show(); updateTitlePosition(); } else { _title.destroy(); } if (wasTitle != hasTitle()) { updateSize(); } } void BoxLayerWidget::setAdditionalTitle(rpl::producer additional) { _additionalTitle = std::move(additional); } void BoxLayerWidget::triggerButton(int index) { if (index < _buttons.size()) { _buttons[index]->clicked(Qt::KeyboardModifiers(), Qt::LeftButton); } } void BoxLayerWidget::setCloseByOutsideClick(bool close) { _closeByOutsideClick = close; } bool BoxLayerWidget::closeByOutsideClick() const { return _closeByOutsideClick; } bool BoxLayerWidget::hasTitle() const { return (_title != nullptr) || !_additionalTitle.current().isEmpty(); } void BoxLayerWidget::showBox( object_ptr box, LayerOptions options, anim::type animated) { _layer->showBox(std::move(box), options, animated); } void BoxLayerWidget::updateSize() { setDimensions(width(), _maxContentHeight); } void BoxLayerWidget::updateButtonsPositions() { if (!_buttons.empty() || _leftButton) { auto padding = _layerType ? st::boxLayerButtonPadding : st::boxButtonPadding; auto right = padding.right(); auto top = buttonsTop(); if (_leftButton) { _leftButton->moveToLeft(right, top); } for (const auto &button : _buttons) { button->moveToRight(right, top); right += button->width() + padding.left(); } } if (_topButton) { _topButton->moveToRight(0, 0); } } QPointer BoxLayerWidget::outerContainer() { return parentWidget(); } void BoxLayerWidget::updateTitlePosition() { _titleLeft = _layerType ? st::boxLayerTitlePosition.x() : st::boxTitlePosition.x(); _titleTop = _layerType ? st::boxLayerTitlePosition.y() : st::boxTitlePosition.y(); if (_title) { _title->resizeToWidth(qMin(_title->naturalWidth(), width() - _titleLeft * 2)); _title->moveToLeft(_titleLeft, _titleTop); } } void BoxLayerWidget::clearButtons() { for (auto &button : base::take(_buttons)) { button.destroy(); } _leftButton.destroy(); _topButton = nullptr; } QPointer BoxLayerWidget::addButton( rpl::producer text, Fn clickCallback, const style::RoundButton &st) { _buttons.emplace_back(this, std::move(text), st); auto result = QPointer(_buttons.back()); result->setClickedCallback(std::move(clickCallback)); result->show(); result->widthValue( ) | rpl::start_with_next([=] { updateButtonsPositions(); }, result->lifetime()); return result; } QPointer BoxLayerWidget::addLeftButton( rpl::producer text, Fn clickCallback, const style::RoundButton &st) { _leftButton = object_ptr(this, std::move(text), st); auto result = QPointer(_leftButton); result->setClickedCallback(std::move(clickCallback)); result->show(); result->widthValue( ) | rpl::start_with_next([=] { updateButtonsPositions(); }, result->lifetime()); return result; } QPointer BoxLayerWidget::addTopButton(const style::IconButton &st, Fn clickCallback) { _topButton = base::make_unique_q(this, st); auto result = QPointer(_topButton.get()); result->setClickedCallback(std::move(clickCallback)); result->show(); updateButtonsPositions(); return result; } void BoxLayerWidget::showLoading(bool show) { const auto &st = st::boxLoadingAnimation; if (!show) { if (_loadingProgress && !_loadingProgress->removeTimer.isActive()) { _loadingProgress->removeTimer.callOnce( st.sineDuration + st.sinePeriod); _loadingProgress->animation.stop(); } return; } if (!_loadingProgress) { const auto callback = [=] { if (!anim::Disabled()) { const auto t = st::boxLoadingAnimation.thickness; update(loadingRect().marginsAdded({ t, t, t, t })); } }; _loadingProgress = std::make_unique( callback, st::boxLoadingAnimation); _loadingProgress->removeTimer.setCallback([=] { _loadingProgress = nullptr; }); } else { _loadingProgress->removeTimer.cancel(); } _loadingProgress->animation.start(); } void BoxLayerWidget::setDimensions(int newWidth, int maxHeight, bool forceCenterPosition) { _maxContentHeight = maxHeight; auto fullHeight = countFullHeight(); if (width() != newWidth || _fullHeight != fullHeight) { _fullHeight = fullHeight; if (parentWidget()) { auto oldGeometry = geometry(); resize(newWidth, countRealHeight()); auto newGeometry = geometry(); auto parentHeight = parentWidget()->height(); if (newGeometry.top() + newGeometry.height() + st::boxVerticalMargin > parentHeight || forceCenterPosition) { const auto top1 = parentHeight - int(st::boxVerticalMargin) - newGeometry.height(); const auto top2 = (parentHeight - newGeometry.height()) / 2; const auto newTop = forceCenterPosition ? std::min(top1, top2) : std::max(top1, top2); if (newTop != newGeometry.top()) { move(newGeometry.left(), newTop); resizeEvent(0); } } parentWidget()->update(oldGeometry.united(geometry()).marginsAdded(st::boxRoundShadow.extend)); } else { resize(newWidth, 0); } } } int BoxLayerWidget::countRealHeight() const { return qMin(_fullHeight, parentWidget()->height() - 2 * st::boxVerticalMargin); } int BoxLayerWidget::countFullHeight() const { return contentTop() + _maxContentHeight + buttonsHeight(); } int BoxLayerWidget::contentTop() const { return hasTitle() ? titleHeight() : (_noContentMargin ? 0 : st::boxTopMargin); } void BoxLayerWidget::resizeEvent(QResizeEvent *e) { updateButtonsPositions(); updateTitlePosition(); auto top = contentTop(); _content->resize(width(), height() - top - buttonsHeight()); _content->moveToLeft(0, top); LayerWidget::resizeEvent(e); } void BoxLayerWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { closeBox(); } else { LayerWidget::keyPressEvent(e); } } } // namespace Ui