init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
This commit is contained in:
455
Telegram/lib_ui/ui/layers/box_content.cpp
Normal file
455
Telegram/lib_ui/ui/layers/box_content.cpp
Normal file
@@ -0,0 +1,455 @@
|
||||
// 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_content.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/rect_part.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "base/timer.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/palette.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
class BoxShow final : public Show {
|
||||
public:
|
||||
explicit BoxShow(not_null<Ui::BoxContent*> box);
|
||||
~BoxShow();
|
||||
|
||||
void showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<BoxContent>,
|
||||
std::unique_ptr<LayerWidget>> &&layer,
|
||||
LayerOptions options,
|
||||
anim::type animated) const override;
|
||||
[[nodiscard]] not_null<QWidget*> toastParent() const override;
|
||||
[[nodiscard]] bool valid() const override;
|
||||
operator bool() const override;
|
||||
|
||||
private:
|
||||
BoxShow(base::weak_qptr<BoxContent> weak, ShowPtr wrapped);
|
||||
|
||||
bool resolve() const;
|
||||
|
||||
const base::weak_qptr<Ui::BoxContent> _weak;
|
||||
mutable std::shared_ptr<Show> _wrapped;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
BoxShow::BoxShow(not_null<BoxContent*> box)
|
||||
: BoxShow(base::make_weak(box.get()), nullptr) {
|
||||
}
|
||||
|
||||
BoxShow::BoxShow(base::weak_qptr<BoxContent> weak, ShowPtr wrapped)
|
||||
: _weak(weak)
|
||||
, _wrapped(std::move(wrapped)) {
|
||||
if (!resolve()) {
|
||||
if (const auto box = _weak.get()) {
|
||||
box->boxClosing(
|
||||
) | rpl::on_next([=] {
|
||||
resolve();
|
||||
_lifetime.destroy();
|
||||
}, _lifetime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BoxShow::~BoxShow() = default;
|
||||
|
||||
bool BoxShow::resolve() const {
|
||||
if (_wrapped) {
|
||||
return true;
|
||||
} else if (const auto strong = _weak.get()) {
|
||||
if (strong->hasDelegate()) {
|
||||
_wrapped = strong->getDelegate()->showFactory()();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BoxShow::showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<BoxContent>,
|
||||
std::unique_ptr<LayerWidget>> &&layer,
|
||||
LayerOptions options,
|
||||
anim::type animated) const {
|
||||
if (resolve()) {
|
||||
_wrapped->showOrHideBoxOrLayer(std::move(layer), options, animated);
|
||||
}
|
||||
}
|
||||
|
||||
not_null<QWidget*> BoxShow::toastParent() const {
|
||||
if (resolve()) {
|
||||
return _wrapped->toastParent();
|
||||
}
|
||||
Unexpected("Stale BoxShow::toastParent call.");
|
||||
}
|
||||
|
||||
bool BoxShow::valid() const {
|
||||
return resolve() && _wrapped->valid();
|
||||
}
|
||||
|
||||
BoxShow::operator bool() const {
|
||||
return valid();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void BoxContent::setTitle(rpl::producer<QString> title) {
|
||||
getDelegate()->setTitle(std::move(title) | rpl::map(Text::WithEntities));
|
||||
}
|
||||
|
||||
QPointer<AbstractButton> BoxContent::addButton(
|
||||
object_ptr<AbstractButton> button) {
|
||||
auto result = QPointer<AbstractButton>(button.data());
|
||||
getDelegate()->addButton(std::move(button));
|
||||
return result;
|
||||
}
|
||||
|
||||
QPointer<RoundButton> BoxContent::addButton(
|
||||
rpl::producer<QString> text,
|
||||
Fn<void()> clickCallback) {
|
||||
return addButton(
|
||||
std::move(text),
|
||||
std::move(clickCallback),
|
||||
getDelegate()->style().button);
|
||||
}
|
||||
|
||||
QPointer<RoundButton> BoxContent::addButton(
|
||||
rpl::producer<QString> text,
|
||||
const style::RoundButton &st) {
|
||||
return addButton(std::move(text), nullptr, st);
|
||||
}
|
||||
|
||||
QPointer<RoundButton> BoxContent::addButton(
|
||||
rpl::producer<QString> text,
|
||||
Fn<void()> clickCallback,
|
||||
const style::RoundButton &st) {
|
||||
auto button = object_ptr<RoundButton>(this, std::move(text), st);
|
||||
auto result = QPointer<RoundButton>(button.data());
|
||||
result->setTextTransform(RoundButton::TextTransform::NoTransform);
|
||||
result->setClickedCallback(std::move(clickCallback));
|
||||
getDelegate()->addButton(std::move(button));
|
||||
return result;
|
||||
}
|
||||
|
||||
QPointer<AbstractButton> BoxContent::addLeftButton(
|
||||
object_ptr<AbstractButton> button) {
|
||||
auto result = QPointer<AbstractButton>(button.data());
|
||||
getDelegate()->addLeftButton(std::move(button));
|
||||
return result;
|
||||
}
|
||||
|
||||
QPointer<RoundButton> BoxContent::addLeftButton(
|
||||
rpl::producer<QString> text,
|
||||
Fn<void()> clickCallback) {
|
||||
return addLeftButton(
|
||||
std::move(text),
|
||||
std::move(clickCallback),
|
||||
getDelegate()->style().button);
|
||||
}
|
||||
|
||||
QPointer<RoundButton> BoxContent::addLeftButton(
|
||||
rpl::producer<QString> text,
|
||||
Fn<void()> clickCallback,
|
||||
const style::RoundButton &st) {
|
||||
auto button = object_ptr<RoundButton>(this, std::move(text), st);
|
||||
const auto result = QPointer<RoundButton>(button.data());
|
||||
result->setTextTransform(RoundButton::TextTransform::NoTransform);
|
||||
result->setClickedCallback(std::move(clickCallback));
|
||||
getDelegate()->addLeftButton(std::move(button));
|
||||
return result;
|
||||
}
|
||||
|
||||
QPointer<AbstractButton> BoxContent::addTopButton(
|
||||
object_ptr<AbstractButton> button) {
|
||||
auto result = QPointer<AbstractButton>(button.data());
|
||||
getDelegate()->addTopButton(std::move(button));
|
||||
return result;
|
||||
}
|
||||
|
||||
QPointer<IconButton> BoxContent::addTopButton(
|
||||
const style::IconButton &st,
|
||||
Fn<void()> clickCallback) {
|
||||
auto button = object_ptr<IconButton>(this, st);
|
||||
const auto result = QPointer<IconButton>(button.data());
|
||||
result->setClickedCallback(std::move(clickCallback));
|
||||
getDelegate()->addTopButton(std::move(button));
|
||||
return result;
|
||||
}
|
||||
|
||||
void BoxContent::setInner(
|
||||
object_ptr<RpWidget> inner,
|
||||
const style::ScrollArea &st) {
|
||||
if (inner) {
|
||||
getDelegate()->setLayerType(true);
|
||||
_scroll.create(this, st);
|
||||
_scroll->setGeometryToLeft(0, _innerTopSkip, width(), 0);
|
||||
_scroll->setOwnedWidget(std::move(inner));
|
||||
if (_topShadow) {
|
||||
_topShadow->raise();
|
||||
_bottomShadow->raise();
|
||||
} else {
|
||||
_topShadow.create(this);
|
||||
_bottomShadow.create(this);
|
||||
}
|
||||
if (!_preparing) {
|
||||
// We didn't set dimensions yet, this will be called from finishPrepare();
|
||||
finishScrollCreate();
|
||||
}
|
||||
} else {
|
||||
getDelegate()->setLayerType(false);
|
||||
_scroll.destroyDelayed();
|
||||
_topShadow.destroyDelayed();
|
||||
_bottomShadow.destroyDelayed();
|
||||
}
|
||||
}
|
||||
|
||||
void BoxContent::finishPrepare() {
|
||||
_preparing = false;
|
||||
if (_scroll) {
|
||||
finishScrollCreate();
|
||||
}
|
||||
setInnerFocus();
|
||||
}
|
||||
|
||||
void BoxContent::finishScrollCreate() {
|
||||
Expects(_scroll != nullptr);
|
||||
|
||||
if (!_scroll->isHidden()) {
|
||||
_scroll->show();
|
||||
}
|
||||
updateScrollAreaGeometry();
|
||||
_scroll->scrolls(
|
||||
) | rpl::on_next([=] {
|
||||
updateInnerVisibleTopBottom();
|
||||
updateShadowsVisibility();
|
||||
}, lifetime());
|
||||
_scroll->innerResizes(
|
||||
) | rpl::on_next([=] {
|
||||
updateInnerVisibleTopBottom();
|
||||
updateShadowsVisibility();
|
||||
}, lifetime());
|
||||
_draggingScroll.scrolls(
|
||||
) | rpl::on_next([=](int delta) {
|
||||
if (_scroll) {
|
||||
_scroll->scrollToY(_scroll->scrollTop() + delta);
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void BoxContent::scrollToWidget(not_null<QWidget*> widget) {
|
||||
if (_scroll) {
|
||||
_scroll->scrollToWidget(widget);
|
||||
}
|
||||
}
|
||||
|
||||
void BoxContent::scrollToY(int top, int bottom) {
|
||||
scrollTo({ top, bottom });
|
||||
}
|
||||
|
||||
void BoxContent::scrollTo(ScrollToRequest request, anim::type animated) {
|
||||
if (_scroll) {
|
||||
const auto v = _scroll->computeScrollToY(request.ymin, request.ymax);
|
||||
const auto now = _scroll->scrollTop();
|
||||
if (animated == anim::type::instant || v == now) {
|
||||
_scrollAnimation.stop();
|
||||
_scroll->scrollToY(v);
|
||||
} else {
|
||||
_scrollAnimation.start([=] {
|
||||
_scroll->scrollToY(_scrollAnimation.value(v));
|
||||
}, now, v, st::slideWrapDuration, anim::sineInOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BoxContent::sendScrollViewportEvent(not_null<QEvent*> event) {
|
||||
if (_scroll) {
|
||||
_scroll->viewportEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> BoxContent::scrolls() const {
|
||||
return _scroll ? _scroll->scrolls() : rpl::never<>();
|
||||
}
|
||||
|
||||
int BoxContent::scrollTop() const {
|
||||
return _scroll ? _scroll->scrollTop() : 0;
|
||||
}
|
||||
|
||||
int BoxContent::scrollHeight() const {
|
||||
return _scroll ? _scroll->height() : 0;
|
||||
}
|
||||
|
||||
base::weak_ptr<Toast::Instance> BoxContent::showToast(
|
||||
Toast::Config &&config) {
|
||||
return BoxShow(this).showToast(std::move(config));
|
||||
}
|
||||
|
||||
base::weak_ptr<Toast::Instance> BoxContent::showToast(
|
||||
TextWithEntities &&text,
|
||||
crl::time duration) {
|
||||
return BoxShow(this).showToast(std::move(text), duration);
|
||||
}
|
||||
|
||||
base::weak_ptr<Toast::Instance> BoxContent::showToast(
|
||||
const QString &text,
|
||||
crl::time duration) {
|
||||
return BoxShow(this).showToast(text, duration);
|
||||
}
|
||||
|
||||
std::shared_ptr<Show> BoxContent::uiShow() {
|
||||
return std::make_shared<BoxShow>(this);
|
||||
}
|
||||
|
||||
void BoxContent::scrollByDraggingDelta(int delta) {
|
||||
_draggingScroll.checkDeltaScroll(_scroll ? delta : 0);
|
||||
}
|
||||
|
||||
void BoxContent::updateInnerVisibleTopBottom() {
|
||||
const auto widget = static_cast<RpWidget*>(_scroll
|
||||
? _scroll->widget()
|
||||
: nullptr);
|
||||
if (widget) {
|
||||
const auto top = _scroll->scrollTop();
|
||||
widget->setVisibleTopBottom(top, top + _scroll->height());
|
||||
}
|
||||
}
|
||||
|
||||
void BoxContent::updateShadowsVisibility(anim::type animated) {
|
||||
if (!_scroll) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto top = _scroll->scrollTop();
|
||||
_topShadow->toggle(
|
||||
((top > 0)
|
||||
|| (_innerTopSkip > 0
|
||||
&& !getDelegate()->style().shadowIgnoreTopSkip)),
|
||||
animated);
|
||||
_bottomShadow->toggle(
|
||||
(top < _scroll->scrollTopMax())
|
||||
|| (_innerBottomSkip > 0
|
||||
&& !getDelegate()->style().shadowIgnoreBottomSkip),
|
||||
animated);
|
||||
}
|
||||
|
||||
void BoxContent::setDimensionsToContent(
|
||||
int newWidth,
|
||||
not_null<RpWidget*> content) {
|
||||
content->resizeToWidth(newWidth);
|
||||
content->heightValue(
|
||||
) | rpl::on_next([=](int height) {
|
||||
setDimensions(newWidth, height);
|
||||
}, content->lifetime());
|
||||
}
|
||||
|
||||
void BoxContent::setInnerTopSkip(int innerTopSkip, bool scrollBottomFixed) {
|
||||
if (_innerTopSkip != innerTopSkip) {
|
||||
const auto delta = innerTopSkip - _innerTopSkip;
|
||||
_innerTopSkip = innerTopSkip;
|
||||
if (_scroll && width() > 0) {
|
||||
auto scrollTopWas = _scroll->scrollTop();
|
||||
updateScrollAreaGeometry();
|
||||
if (scrollBottomFixed) {
|
||||
_scroll->scrollToY(scrollTopWas + delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BoxContent::setInnerBottomSkip(int innerBottomSkip) {
|
||||
if (_innerBottomSkip != innerBottomSkip) {
|
||||
_innerBottomSkip = innerBottomSkip;
|
||||
if (_scroll && width() > 0) {
|
||||
updateScrollAreaGeometry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BoxContent::setInnerVisible(bool scrollAreaVisible) {
|
||||
if (_scroll) {
|
||||
_scroll->setVisible(scrollAreaVisible);
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap BoxContent::grabInnerCache() {
|
||||
const auto isTopShadowVisible = !_topShadow->isHidden();
|
||||
const auto isBottomShadowVisible = !_bottomShadow->isHidden();
|
||||
if (isTopShadowVisible) {
|
||||
_topShadow->setVisible(false);
|
||||
}
|
||||
if (isBottomShadowVisible) {
|
||||
_bottomShadow->setVisible(false);
|
||||
}
|
||||
const auto result = GrabWidget(this, _scroll->geometry());
|
||||
if (isTopShadowVisible) {
|
||||
_topShadow->setVisible(true);
|
||||
}
|
||||
if (isBottomShadowVisible) {
|
||||
_bottomShadow->setVisible(true);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void BoxContent::resizeEvent(QResizeEvent *e) {
|
||||
if (_scroll) {
|
||||
updateScrollAreaGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
void BoxContent::keyPressEvent(QKeyEvent *e) {
|
||||
if (e->key() == Qt::Key_Escape && !_closeByEscape) {
|
||||
e->accept();
|
||||
} else {
|
||||
RpWidget::keyPressEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void BoxContent::updateScrollAreaGeometry() {
|
||||
const auto newScrollHeight = height() - _innerTopSkip - _innerBottomSkip;
|
||||
const auto changed = (_scroll->height() != newScrollHeight);
|
||||
_scroll->setGeometryToLeft(0, _innerTopSkip, width(), newScrollHeight);
|
||||
_topShadow->entity()->resize(width(), st::lineWidth);
|
||||
_topShadow->moveToLeft(0, _innerTopSkip);
|
||||
_bottomShadow->entity()->resize(width(), st::lineWidth);
|
||||
_bottomShadow->moveToLeft(
|
||||
0,
|
||||
height() - _innerBottomSkip - st::lineWidth);
|
||||
if (changed) {
|
||||
updateInnerVisibleTopBottom();
|
||||
updateShadowsVisibility(anim::type::instant);
|
||||
}
|
||||
}
|
||||
|
||||
object_ptr<RpWidget> BoxContent::doTakeInnerWidget() {
|
||||
return _scroll->takeWidget<RpWidget>();
|
||||
}
|
||||
|
||||
void BoxContent::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
if (testAttribute(Qt::WA_OpaquePaintEvent)) {
|
||||
const auto &color = getDelegate()->style().bg;
|
||||
for (const auto &rect : e->region()) {
|
||||
p.fillRect(rect, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
396
Telegram/lib_ui/ui/layers/box_content.h
Normal file
396
Telegram/lib_ui/ui/layers/box_content.h
Normal file
@@ -0,0 +1,396 @@
|
||||
// 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
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
#include "base/flags.h"
|
||||
#include "ui/dragging_scroll_manager.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/layers/layer_widget.h"
|
||||
#include "ui/layers/show.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/animation_value.h"
|
||||
#include "ui/text/text_entity.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
enum class RectPart;
|
||||
using RectParts = base::flags<RectPart>;
|
||||
|
||||
namespace base {
|
||||
class Timer;
|
||||
} // namespace base
|
||||
|
||||
namespace style {
|
||||
struct RoundButton;
|
||||
struct IconButton;
|
||||
struct ScrollArea;
|
||||
struct Box;
|
||||
} // namespace style
|
||||
|
||||
namespace st {
|
||||
extern const style::ScrollArea &boxScroll;
|
||||
} // namespace st
|
||||
|
||||
namespace Ui::Toast {
|
||||
struct Config;
|
||||
class Instance;
|
||||
} // namespace Ui::Toast
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
} // namespace Ui
|
||||
|
||||
template <typename BoxType = Ui::GenericBox, typename ...Args>
|
||||
inline object_ptr<BoxType> Box(Args &&...args) {
|
||||
const auto parent = static_cast<QWidget*>(nullptr);
|
||||
return object_ptr<BoxType>(parent, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class AbstractButton;
|
||||
class RoundButton;
|
||||
class IconButton;
|
||||
class ScrollArea;
|
||||
class FlatLabel;
|
||||
class FadeShadow;
|
||||
class BoxContent;
|
||||
struct ScrollToRequest;
|
||||
|
||||
class BoxContentDelegate {
|
||||
public:
|
||||
virtual void setLayerType(bool layerType) = 0;
|
||||
virtual void setStyle(const style::Box &st) = 0;
|
||||
virtual const style::Box &style() = 0;
|
||||
virtual void setTitle(rpl::producer<TextWithEntities> title) = 0;
|
||||
virtual void setAdditionalTitle(rpl::producer<QString> additional) = 0;
|
||||
virtual void setCloseByOutsideClick(bool close) = 0;
|
||||
|
||||
virtual void setCustomCornersFilling(RectParts corners) = 0;
|
||||
virtual void clearButtons() = 0;
|
||||
virtual void addButton(object_ptr<AbstractButton> button) = 0;
|
||||
virtual void addLeftButton(object_ptr<AbstractButton> button) = 0;
|
||||
virtual void addTopButton(object_ptr<AbstractButton> button) = 0;
|
||||
virtual void showLoading(bool show) = 0;
|
||||
virtual void updateButtonsPositions() = 0;
|
||||
|
||||
virtual void showBox(
|
||||
object_ptr<BoxContent> box,
|
||||
LayerOptions options,
|
||||
anim::type animated) = 0;
|
||||
virtual void setDimensions(
|
||||
int newWidth,
|
||||
int maxHeight,
|
||||
bool forceCenterPosition = false) = 0;
|
||||
virtual void setNoContentMargin(bool noContentMargin) = 0;
|
||||
virtual bool isBoxShown() const = 0;
|
||||
virtual void closeBox() = 0;
|
||||
virtual void hideLayer() = 0;
|
||||
virtual void triggerButton(int index) = 0;
|
||||
|
||||
template <typename BoxType>
|
||||
base::weak_qptr<BoxType> show(
|
||||
object_ptr<BoxType> content,
|
||||
LayerOptions options = LayerOption::KeepOther,
|
||||
anim::type animated = anim::type::normal) {
|
||||
auto result = base::weak_qptr<BoxType>(content.data());
|
||||
showBox(std::move(content), options, animated);
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual ShowFactory showFactory() = 0;
|
||||
virtual QPointer<QWidget> outerContainer() = 0;
|
||||
|
||||
};
|
||||
|
||||
class BoxContent : public RpWidget {
|
||||
public:
|
||||
BoxContent() {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
}
|
||||
|
||||
QAccessible::Role accessibilityRole() override {
|
||||
return QAccessible::Role::Dialog;
|
||||
}
|
||||
|
||||
bool isBoxShown() const {
|
||||
return getDelegate()->isBoxShown();
|
||||
}
|
||||
void closeBox() {
|
||||
getDelegate()->closeBox();
|
||||
}
|
||||
void triggerButton(int index) {
|
||||
getDelegate()->triggerButton(index);
|
||||
}
|
||||
|
||||
void setTitle(rpl::producer<QString> title);
|
||||
void setTitle(rpl::producer<TextWithEntities> title) {
|
||||
getDelegate()->setTitle(std::move(title));
|
||||
}
|
||||
void setAdditionalTitle(rpl::producer<QString> additional) {
|
||||
getDelegate()->setAdditionalTitle(std::move(additional));
|
||||
}
|
||||
void setCloseByEscape(bool close) {
|
||||
_closeByEscape = close;
|
||||
}
|
||||
void setCloseByOutsideClick(bool close) {
|
||||
getDelegate()->setCloseByOutsideClick(close);
|
||||
}
|
||||
|
||||
void scrollToWidget(not_null<QWidget*> widget);
|
||||
|
||||
virtual void showFinished() {
|
||||
}
|
||||
void setCustomCornersFilling(RectParts corners) {
|
||||
getDelegate()->setCustomCornersFilling(corners);
|
||||
}
|
||||
void clearButtons() {
|
||||
getDelegate()->clearButtons();
|
||||
}
|
||||
QPointer<AbstractButton> addButton(object_ptr<AbstractButton> button);
|
||||
QPointer<RoundButton> addButton(
|
||||
rpl::producer<QString> text,
|
||||
Fn<void()> clickCallback = nullptr);
|
||||
QPointer<RoundButton> addButton(
|
||||
rpl::producer<QString> text,
|
||||
const style::RoundButton &st);
|
||||
QPointer<RoundButton> addButton(
|
||||
rpl::producer<QString> text,
|
||||
Fn<void()> clickCallback,
|
||||
const style::RoundButton &st);
|
||||
QPointer<AbstractButton> addLeftButton(
|
||||
object_ptr<AbstractButton> button);
|
||||
QPointer<RoundButton> addLeftButton(
|
||||
rpl::producer<QString> text,
|
||||
Fn<void()> clickCallback = nullptr);
|
||||
QPointer<RoundButton> addLeftButton(
|
||||
rpl::producer<QString> text,
|
||||
Fn<void()> clickCallback,
|
||||
const style::RoundButton& st);
|
||||
QPointer<AbstractButton> addTopButton(
|
||||
object_ptr<AbstractButton> button);
|
||||
QPointer<IconButton> addTopButton(
|
||||
const style::IconButton &st,
|
||||
Fn<void()> clickCallback = nullptr);
|
||||
void showLoading(bool show) {
|
||||
getDelegate()->showLoading(show);
|
||||
}
|
||||
void updateButtonsGeometry() {
|
||||
getDelegate()->updateButtonsPositions();
|
||||
}
|
||||
void setStyle(const style::Box &st) {
|
||||
getDelegate()->setStyle(st);
|
||||
}
|
||||
|
||||
virtual void setInnerFocus() {
|
||||
setFocus();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<> boxClosing() const {
|
||||
return _boxClosingStream.events();
|
||||
}
|
||||
void notifyBoxClosing() {
|
||||
_boxClosingStream.fire({});
|
||||
}
|
||||
|
||||
void setDelegate(not_null<BoxContentDelegate*> newDelegate) {
|
||||
_delegate = newDelegate;
|
||||
_preparing = true;
|
||||
prepare();
|
||||
finishPrepare();
|
||||
}
|
||||
[[nodiscard]] bool hasDelegate() const {
|
||||
return _delegate != nullptr;
|
||||
}
|
||||
[[nodiscard]] not_null<BoxContentDelegate*> getDelegate() const {
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
void setNoContentMargin(bool noContentMargin) {
|
||||
if (_noContentMargin != noContentMargin) {
|
||||
_noContentMargin = noContentMargin;
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, !_noContentMargin);
|
||||
}
|
||||
getDelegate()->setNoContentMargin(noContentMargin);
|
||||
}
|
||||
|
||||
void scrollByDraggingDelta(int delta);
|
||||
|
||||
void scrollToY(int top, int bottom = -1);
|
||||
void scrollTo(
|
||||
ScrollToRequest request,
|
||||
anim::type animated = anim::type::instant);
|
||||
void sendScrollViewportEvent(not_null<QEvent*> event);
|
||||
[[nodiscard]] rpl::producer<> scrolls() const;
|
||||
[[nodiscard]] int scrollTop() const;
|
||||
[[nodiscard]] int scrollHeight() const;
|
||||
|
||||
base::weak_ptr<Toast::Instance> showToast(Toast::Config &&config);
|
||||
base::weak_ptr<Toast::Instance> showToast(
|
||||
TextWithEntities &&text,
|
||||
crl::time duration = 0);
|
||||
base::weak_ptr<Toast::Instance> showToast(
|
||||
const QString &text,
|
||||
crl::time duration = 0);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Show> uiShow();
|
||||
|
||||
protected:
|
||||
virtual void prepare() = 0;
|
||||
|
||||
void setLayerType(bool layerType) {
|
||||
getDelegate()->setLayerType(layerType);
|
||||
}
|
||||
|
||||
void setDimensions(
|
||||
int newWidth,
|
||||
int maxHeight,
|
||||
bool forceCenterPosition = false) {
|
||||
getDelegate()->setDimensions(
|
||||
newWidth,
|
||||
maxHeight,
|
||||
forceCenterPosition);
|
||||
}
|
||||
void setDimensionsToContent(
|
||||
int newWidth,
|
||||
not_null<RpWidget*> content);
|
||||
void setInnerTopSkip(int topSkip, bool scrollBottomFixed = false);
|
||||
void setInnerBottomSkip(int bottomSkip);
|
||||
|
||||
template <typename Widget>
|
||||
QPointer<Widget> setInnerWidget(
|
||||
object_ptr<Widget> inner,
|
||||
const style::ScrollArea &st,
|
||||
int topSkip = 0,
|
||||
int bottomSkip = 0) {
|
||||
auto result = QPointer<Widget>(inner.data());
|
||||
setInnerTopSkip(topSkip);
|
||||
setInnerBottomSkip(bottomSkip);
|
||||
setInner(std::move(inner), st);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Widget>
|
||||
QPointer<Widget> setInnerWidget(
|
||||
object_ptr<Widget> inner,
|
||||
int topSkip = 0,
|
||||
int bottomSkip = 0) {
|
||||
return setInnerWidget(
|
||||
std::move(inner),
|
||||
st::boxScroll,
|
||||
topSkip,
|
||||
bottomSkip);
|
||||
}
|
||||
|
||||
template <typename Widget>
|
||||
object_ptr<Widget> takeInnerWidget() {
|
||||
return object_ptr<Widget>::fromRaw(
|
||||
static_cast<Widget*>(doTakeInnerWidget().release()));
|
||||
}
|
||||
|
||||
void setInnerVisible(bool scrollAreaVisible);
|
||||
QPixmap grabInnerCache();
|
||||
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
|
||||
private:
|
||||
void finishPrepare();
|
||||
void finishScrollCreate();
|
||||
void setInner(object_ptr<RpWidget> inner, const style::ScrollArea &st);
|
||||
void updateScrollAreaGeometry();
|
||||
void updateInnerVisibleTopBottom();
|
||||
void updateShadowsVisibility(anim::type animated = anim::type::normal);
|
||||
object_ptr<RpWidget> doTakeInnerWidget();
|
||||
|
||||
BoxContentDelegate *_delegate = nullptr;
|
||||
|
||||
bool _preparing = false;
|
||||
bool _noContentMargin = false;
|
||||
bool _closeByEscape = true;
|
||||
int _innerTopSkip = 0;
|
||||
int _innerBottomSkip = 0;
|
||||
object_ptr<ScrollArea> _scroll = { nullptr };
|
||||
object_ptr<FadeShadow> _topShadow = { nullptr };
|
||||
object_ptr<FadeShadow> _bottomShadow = { nullptr };
|
||||
|
||||
Ui::DraggingScrollManager _draggingScroll;
|
||||
Ui::Animations::Simple _scrollAnimation;
|
||||
|
||||
rpl::event_stream<> _boxClosingStream;
|
||||
|
||||
};
|
||||
|
||||
class BoxPointer {
|
||||
public:
|
||||
BoxPointer() = default;
|
||||
BoxPointer(const BoxPointer &other) = default;
|
||||
BoxPointer(BoxPointer &&other) : _value(base::take(other._value)) {
|
||||
}
|
||||
BoxPointer(BoxContent *value) : _value(value) {
|
||||
}
|
||||
BoxPointer(base::weak_qptr<BoxContent> value) : _value(value) {
|
||||
}
|
||||
BoxPointer &operator=(const BoxPointer &other) {
|
||||
if (_value != other._value) {
|
||||
destroy();
|
||||
_value = other._value;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
BoxPointer &operator=(BoxPointer &&other) {
|
||||
if (_value != other._value) {
|
||||
destroy();
|
||||
_value = base::take(other._value);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
BoxPointer &operator=(BoxContent *other) {
|
||||
if (_value != other) {
|
||||
destroy();
|
||||
_value = other;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
BoxPointer &operator=(base::weak_qptr<BoxContent> other) {
|
||||
if (_value != other) {
|
||||
destroy();
|
||||
_value = other;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
~BoxPointer() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
BoxContent *get() const {
|
||||
return _value.get();
|
||||
}
|
||||
operator BoxContent*() const {
|
||||
return get();
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return get();
|
||||
}
|
||||
BoxContent *operator->() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
private:
|
||||
void destroy() {
|
||||
if (const auto value = base::take(_value)) {
|
||||
value->closeBox();
|
||||
}
|
||||
}
|
||||
|
||||
base::weak_qptr<BoxContent> _value;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
||||
427
Telegram/lib_ui/ui/layers/box_layer_widget.cpp
Normal file
427
Telegram/lib_ui/ui/layers/box_layer_widget.cpp
Normal file
@@ -0,0 +1,427 @@
|
||||
// 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/labels.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<void()> &&callback,
|
||||
const style::InfiniteRadialAnimation &st);
|
||||
|
||||
InfiniteRadialAnimation animation;
|
||||
base::Timer removeTimer;
|
||||
};
|
||||
|
||||
BoxLayerWidget::LoadingProgress::LoadingProgress(
|
||||
Fn<void()> &&callback,
|
||||
const style::InfiniteRadialAnimation &st)
|
||||
: animation(std::move(callback), st) {
|
||||
}
|
||||
|
||||
BoxLayerWidget::BoxLayerWidget(
|
||||
not_null<LayerStackWidget*> layer,
|
||||
object_ptr<BoxContent> content)
|
||||
: LayerWidget(layer)
|
||||
, _layer(layer)
|
||||
, _content(std::move(content))
|
||||
, _roundRect(st::boxRadius, st().bg) {
|
||||
_content->setParent(this);
|
||||
_content->setDelegate(this);
|
||||
|
||||
_additionalTitle.changes(
|
||||
) | rpl::on_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 st::boxTitleHeight;
|
||||
}
|
||||
|
||||
const style::Box &BoxLayerWidget::st() const {
|
||||
return _st
|
||||
? *_st
|
||||
: _layerType
|
||||
? (_layer->boxStyleOverrideLayer()
|
||||
? *_layer->boxStyleOverrideLayer()
|
||||
: st::layerBox)
|
||||
: (_layer->boxStyleOverride()
|
||||
? *_layer->boxStyleOverride()
|
||||
: st::defaultBox);
|
||||
}
|
||||
|
||||
void BoxLayerWidget::setStyle(const style::Box &st) {
|
||||
_st = &st;
|
||||
_roundRect.setColor(st.bg);
|
||||
}
|
||||
|
||||
const style::Box &BoxLayerWidget::style() {
|
||||
return st();
|
||||
}
|
||||
|
||||
int BoxLayerWidget::buttonsHeight() const {
|
||||
const auto padding = st().buttonPadding;
|
||||
return padding.top() + st().buttonHeight + padding.bottom();
|
||||
}
|
||||
|
||||
int BoxLayerWidget::buttonsTop() const {
|
||||
const auto padding = st().buttonPadding;
|
||||
return height() - padding.bottom() - st().buttonHeight;
|
||||
}
|
||||
|
||||
QRect BoxLayerWidget::loadingRect() const {
|
||||
const auto padding = st().buttonPadding;
|
||||
const auto size = st::boxLoadingSize;
|
||||
const auto skipx = st::boxTitlePosition.x();
|
||||
const auto skipy = (st().buttonHeight - size) / 2;
|
||||
return QRect(
|
||||
skipx,
|
||||
height() - padding.bottom() - skipy - size,
|
||||
size,
|
||||
size);
|
||||
}
|
||||
|
||||
void BoxLayerWidget::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
const auto clip = e->rect();
|
||||
const auto paintTopRounded = !(_customCornersFilling & RectPart::FullTop)
|
||||
&& clip.intersects(QRect(0, 0, width(), st::boxRadius));
|
||||
const auto paintBottomRounded = !(_customCornersFilling
|
||||
& RectPart::FullBottom)
|
||||
&& clip.intersects(
|
||||
QRect(0, height() - st::boxRadius, width(), st::boxRadius));
|
||||
if (paintTopRounded || paintBottomRounded) {
|
||||
_roundRect.paint(p, rect(), RectPart::None
|
||||
| (paintTopRounded ? RectPart::FullTop : RectPart::None)
|
||||
| (paintBottomRounded ? RectPart::FullBottom : RectPart::None));
|
||||
}
|
||||
const auto other = e->region().intersected(
|
||||
QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius));
|
||||
if (!other.isEmpty()) {
|
||||
for (const auto &rect : other) {
|
||||
p.fillRect(rect, st().bg);
|
||||
}
|
||||
}
|
||||
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::boxTitleAdditionalFont);
|
||||
p.setPen(st().titleAdditionalFg);
|
||||
p.drawTextLeft(
|
||||
_titleLeft + (_title ? _title->width() : 0) + st::boxTitleAdditionalSkip,
|
||||
_titleTop + st::boxTitleFont->ascent - st::boxTitleAdditionalFont->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<TextWithEntities> title) {
|
||||
const auto wasTitle = hasTitle();
|
||||
if (title) {
|
||||
_title.create(this, rpl::duplicate(title), st().title);
|
||||
_title->show();
|
||||
std::move(
|
||||
title
|
||||
) | rpl::on_next([=] {
|
||||
updateTitlePosition();
|
||||
}, _title->lifetime());
|
||||
} else {
|
||||
_title.destroy();
|
||||
}
|
||||
if (wasTitle != hasTitle()) {
|
||||
updateSize();
|
||||
}
|
||||
}
|
||||
|
||||
void BoxLayerWidget::setAdditionalTitle(rpl::producer<QString> 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<BoxContent> box,
|
||||
LayerOptions options,
|
||||
anim::type animated) {
|
||||
_layer->showBox(std::move(box), options, animated);
|
||||
}
|
||||
|
||||
void BoxLayerWidget::hideLayer() {
|
||||
_layer->hideLayers(anim::type::normal);
|
||||
}
|
||||
|
||||
void BoxLayerWidget::updateSize() {
|
||||
setDimensions(width(), _maxContentHeight);
|
||||
}
|
||||
|
||||
void BoxLayerWidget::updateButtonsPositions() {
|
||||
if (!_buttons.empty() || _leftButton) {
|
||||
auto padding = st().buttonPadding;
|
||||
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();
|
||||
}
|
||||
}
|
||||
auto right = 0;
|
||||
for (const auto &button : _topButtons) {
|
||||
button->moveToRight(right, 0);
|
||||
right += button->width();
|
||||
}
|
||||
}
|
||||
|
||||
ShowFactory BoxLayerWidget::showFactory() {
|
||||
return _layer->showFactory();
|
||||
}
|
||||
|
||||
QPointer<QWidget> BoxLayerWidget::outerContainer() {
|
||||
return parentWidget();
|
||||
}
|
||||
|
||||
void BoxLayerWidget::updateTitlePosition() {
|
||||
_titleLeft = st::boxTitlePosition.x();
|
||||
_titleTop = st::boxTitlePosition.y();
|
||||
if (_title) {
|
||||
auto topButtonsSkip = 0;
|
||||
for (const auto &button : _topButtons) {
|
||||
topButtonsSkip += button->width();
|
||||
}
|
||||
_title->resizeToNaturalWidth(
|
||||
width() - _titleLeft * 2 - topButtonsSkip);
|
||||
_title->moveToLeft(_titleLeft, _titleTop);
|
||||
}
|
||||
}
|
||||
|
||||
void BoxLayerWidget::setCustomCornersFilling(RectParts corners) {
|
||||
_customCornersFilling = corners;
|
||||
update();
|
||||
}
|
||||
|
||||
void BoxLayerWidget::clearButtons() {
|
||||
for (auto &button : base::take(_buttons)) {
|
||||
button.destroy();
|
||||
}
|
||||
_leftButton.destroy();
|
||||
base::take(_topButtons);
|
||||
}
|
||||
|
||||
void BoxLayerWidget::addButton(object_ptr<AbstractButton> button) {
|
||||
_buttons.push_back(std::move(button));
|
||||
const auto raw = _buttons.back().data();
|
||||
raw->setParent(this);
|
||||
raw->show();
|
||||
if (st().buttonWide) {
|
||||
widthValue() | rpl::on_next([=](int width) {
|
||||
const auto buttonWidth = width
|
||||
- st().buttonPadding.left()
|
||||
- st().buttonPadding.right();
|
||||
if (buttonWidth > 0) {
|
||||
raw->resizeToWidth(buttonWidth);
|
||||
}
|
||||
}, raw->lifetime());
|
||||
}
|
||||
raw->widthValue(
|
||||
) | rpl::on_next([=] {
|
||||
if (st().buttonWide) {
|
||||
const auto buttonWidth = width()
|
||||
- st().buttonPadding.left()
|
||||
- st().buttonPadding.right();
|
||||
if (buttonWidth > 0 && raw->width() != buttonWidth) {
|
||||
raw->resizeToWidth(buttonWidth);
|
||||
}
|
||||
}
|
||||
updateButtonsPositions();
|
||||
}, raw->lifetime());
|
||||
}
|
||||
|
||||
void BoxLayerWidget::addLeftButton(object_ptr<AbstractButton> button) {
|
||||
_leftButton = std::move(button);
|
||||
const auto raw = _leftButton.data();
|
||||
raw->setParent(this);
|
||||
raw->show();
|
||||
raw->widthValue(
|
||||
) | rpl::on_next([=] {
|
||||
updateButtonsPositions();
|
||||
}, raw->lifetime());
|
||||
}
|
||||
|
||||
void BoxLayerWidget::addTopButton(object_ptr<AbstractButton> button) {
|
||||
_topButtons.push_back(base::unique_qptr<AbstractButton>(button.release()));
|
||||
const auto raw = _topButtons.back().get();
|
||||
raw->setParent(this);
|
||||
raw->show();
|
||||
updateButtonsPositions();
|
||||
updateTitlePosition();
|
||||
}
|
||||
|
||||
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<LoadingProgress>(
|
||||
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();
|
||||
const auto bottomMargin = st().margin.bottom();
|
||||
if (newGeometry.top() + newGeometry.height() + bottomMargin > parentHeight
|
||||
|| forceCenterPosition) {
|
||||
const auto top1 = parentHeight - bottomMargin - 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 {
|
||||
const auto &margin = st().margin;
|
||||
return std::min(
|
||||
_fullHeight,
|
||||
parentWidget()->height() - margin.top() - margin.bottom());
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
const 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
|
||||
143
Telegram/lib_ui/ui/layers/box_layer_widget.h
Normal file
143
Telegram/lib_ui/ui/layers/box_layer_widget.h
Normal file
@@ -0,0 +1,143 @@
|
||||
// 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
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
#include "base/flags.h"
|
||||
#include "ui/layers/layer_widget.h"
|
||||
#include "ui/layers/box_content.h"
|
||||
#include "ui/round_rect.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
class Painter;
|
||||
struct TextWithEntities;
|
||||
|
||||
namespace anim {
|
||||
enum class type : uchar;
|
||||
} // namespace anim
|
||||
|
||||
namespace style {
|
||||
struct Box;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class AbstractButton;
|
||||
class FlatLabel;
|
||||
|
||||
class BoxLayerWidget : public LayerWidget, public BoxContentDelegate {
|
||||
public:
|
||||
BoxLayerWidget(
|
||||
not_null<LayerStackWidget*> layer,
|
||||
object_ptr<BoxContent> content);
|
||||
~BoxLayerWidget();
|
||||
|
||||
void parentResized() override;
|
||||
|
||||
void setLayerType(bool layerType) override;
|
||||
void setStyle(const style::Box &st) override;
|
||||
const style::Box &style() override;
|
||||
void setTitle(rpl::producer<TextWithEntities> title) override;
|
||||
void setAdditionalTitle(rpl::producer<QString> additional) override;
|
||||
void showBox(
|
||||
object_ptr<BoxContent> box,
|
||||
LayerOptions options,
|
||||
anim::type animated) override;
|
||||
|
||||
void showFinished() override {
|
||||
_content->showFinished();
|
||||
}
|
||||
|
||||
void setCustomCornersFilling(RectParts corners) override;
|
||||
void clearButtons() override;
|
||||
void addButton(object_ptr<AbstractButton> button) override;
|
||||
void addLeftButton(object_ptr<AbstractButton> button) override;
|
||||
void addTopButton(object_ptr<AbstractButton> button) override;
|
||||
void showLoading(bool show) override;
|
||||
void updateButtonsPositions() override;
|
||||
ShowFactory showFactory() override;
|
||||
QPointer<QWidget> outerContainer() override;
|
||||
|
||||
void setDimensions(
|
||||
int newWidth,
|
||||
int maxHeight,
|
||||
bool forceCenterPosition = false) override;
|
||||
|
||||
void setNoContentMargin(bool noContentMargin) override {
|
||||
if (_noContentMargin != noContentMargin) {
|
||||
_noContentMargin = noContentMargin;
|
||||
updateSize();
|
||||
}
|
||||
}
|
||||
|
||||
bool isBoxShown() const override {
|
||||
return !isHidden();
|
||||
}
|
||||
void closeBox() override {
|
||||
closeLayer();
|
||||
}
|
||||
void hideLayer() override;
|
||||
void triggerButton(int index) override;
|
||||
|
||||
void setCloseByOutsideClick(bool close) override;
|
||||
bool closeByOutsideClick() const override;
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
void doSetInnerFocus() override {
|
||||
_content->setInnerFocus();
|
||||
}
|
||||
void closeHook() override {
|
||||
_content->notifyBoxClosing();
|
||||
}
|
||||
|
||||
private:
|
||||
struct LoadingProgress;
|
||||
|
||||
void paintAdditionalTitle(Painter &p);
|
||||
void updateTitlePosition();
|
||||
|
||||
[[nodiscard]] const style::Box &st() const;
|
||||
[[nodiscard]] bool hasTitle() const;
|
||||
[[nodiscard]] int titleHeight() const;
|
||||
[[nodiscard]] int buttonsHeight() const;
|
||||
[[nodiscard]] int buttonsTop() const;
|
||||
[[nodiscard]] int contentTop() const;
|
||||
[[nodiscard]] int countFullHeight() const;
|
||||
[[nodiscard]] int countRealHeight() const;
|
||||
[[nodiscard]] QRect loadingRect() const;
|
||||
void updateSize();
|
||||
|
||||
const style::Box *_st = nullptr;
|
||||
not_null<LayerStackWidget*> _layer;
|
||||
bool _layerType = false;
|
||||
int _fullHeight = 0;
|
||||
|
||||
bool _noContentMargin = false;
|
||||
int _maxContentHeight = 0;
|
||||
object_ptr<BoxContent> _content;
|
||||
|
||||
RoundRect _roundRect;
|
||||
object_ptr<FlatLabel> _title = { nullptr };
|
||||
Fn<TextWithEntities()> _titleFactory;
|
||||
rpl::variable<QString> _additionalTitle;
|
||||
RectParts _customCornersFilling;
|
||||
int _titleLeft = 0;
|
||||
int _titleTop = 0;
|
||||
bool _closeByOutsideClick = true;
|
||||
|
||||
std::vector<object_ptr<AbstractButton>> _buttons;
|
||||
object_ptr<AbstractButton> _leftButton = { nullptr };
|
||||
std::vector<base::unique_qptr<AbstractButton>> _topButtons;
|
||||
std::unique_ptr<LoadingProgress> _loadingProgress;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
||||
131
Telegram/lib_ui/ui/layers/generic_box.cpp
Normal file
131
Telegram/lib_ui/ui/layers/generic_box.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
// 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/generic_box.h"
|
||||
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/wrap.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void GenericBox::prepare() {
|
||||
_init(this);
|
||||
|
||||
const auto currentWidth = width();
|
||||
const auto pinnedToTop = _pinnedToTopContent.data();
|
||||
const auto pinnedToBottom = _pinnedToBottomContent.data();
|
||||
if (pinnedToTop) {
|
||||
pinnedToTop->resizeToWidth(currentWidth);
|
||||
}
|
||||
if (pinnedToBottom) {
|
||||
pinnedToBottom->resizeToWidth(currentWidth);
|
||||
}
|
||||
|
||||
auto wrap = object_ptr<Ui::OverrideMargins>(this, std::move(_owned));
|
||||
wrap->resizeToWidth(currentWidth);
|
||||
rpl::combine(
|
||||
pinnedToTop ? pinnedToTop->heightValue() : rpl::single(0),
|
||||
wrap->heightValue(),
|
||||
pinnedToBottom ? pinnedToBottom->heightValue() : rpl::single(0)
|
||||
) | rpl::on_next([=](int top, int height, int bottom) {
|
||||
Expects(_minHeight >= 0);
|
||||
Expects(!_maxHeight || _minHeight <= _maxHeight);
|
||||
|
||||
setInnerTopSkip(top);
|
||||
setInnerBottomSkip(bottom);
|
||||
const auto desired = top + height + bottom;
|
||||
setDimensions(
|
||||
currentWidth,
|
||||
std::clamp(
|
||||
desired,
|
||||
_minHeight,
|
||||
_maxHeight ? _maxHeight : std::max(_minHeight, desired)),
|
||||
true);
|
||||
}, wrap->lifetime());
|
||||
|
||||
setInnerWidget(
|
||||
std::move(wrap),
|
||||
_scrollSt ? *_scrollSt : st::boxScroll,
|
||||
pinnedToTop ? pinnedToTop->height() : 0,
|
||||
pinnedToBottom ? pinnedToBottom->height() : 0);
|
||||
|
||||
if (pinnedToBottom) {
|
||||
rpl::combine(
|
||||
heightValue(),
|
||||
pinnedToBottom->heightValue()
|
||||
) | rpl::on_next([=](int outer, int height) {
|
||||
pinnedToBottom->move(0, outer - height);
|
||||
}, pinnedToBottom->lifetime());
|
||||
}
|
||||
|
||||
if (const auto onstack = _initScroll) {
|
||||
onstack();
|
||||
}
|
||||
}
|
||||
|
||||
void GenericBox::addSkip(int height) {
|
||||
addRow(object_ptr<Ui::FixedHeightWidget>(this, height));
|
||||
}
|
||||
|
||||
void GenericBox::setInnerFocus() {
|
||||
if (_focus) {
|
||||
_focus();
|
||||
} else {
|
||||
BoxContent::setInnerFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void GenericBox::showFinished() {
|
||||
const auto guard = QPointer(this);
|
||||
if (const auto onstack = _showFinished) {
|
||||
onstack();
|
||||
if (!guard) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_showFinishes.fire({});
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> GenericBox::doSetPinnedToTopContent(
|
||||
object_ptr<Ui::RpWidget> content) {
|
||||
_pinnedToTopContent = std::move(content);
|
||||
return _pinnedToTopContent.data();
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> GenericBox::doSetPinnedToBottomContent(
|
||||
object_ptr<Ui::RpWidget> content) {
|
||||
_pinnedToBottomContent = std::move(content);
|
||||
return _pinnedToBottomContent.data();
|
||||
}
|
||||
|
||||
int GenericBox::rowsCount() const {
|
||||
return _content->count();
|
||||
}
|
||||
|
||||
int GenericBox::width() const {
|
||||
return _width ? _width : st::boxWidth;
|
||||
}
|
||||
|
||||
not_null<Ui::VerticalLayout*> GenericBox::verticalLayout() {
|
||||
return _content;
|
||||
}
|
||||
|
||||
rpl::producer<> BoxShowFinishes(not_null<GenericBox*> box) {
|
||||
const auto singleShot = box->lifetime().make_state<rpl::lifetime>();
|
||||
const auto showFinishes = singleShot->make_state<rpl::event_stream<>>();
|
||||
|
||||
box->setShowFinishedCallback([=] {
|
||||
showFinishes->fire({});
|
||||
singleShot->destroy();
|
||||
box->setShowFinishedCallback(nullptr);
|
||||
});
|
||||
|
||||
return showFinishes->events();
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
230
Telegram/lib_ui/ui/layers/generic_box.h
Normal file
230
Telegram/lib_ui/ui/layers/generic_box.h
Normal file
@@ -0,0 +1,230 @@
|
||||
// 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
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "ui/layers/box_content.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace st {
|
||||
extern const style::margins &boxRowPadding;
|
||||
} // namespace st
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class GenericBox final : public BoxContent {
|
||||
public:
|
||||
// InitMethod::operator()(not_null<GenericBox*> box, InitArgs...)
|
||||
// init(box, args...)
|
||||
template <
|
||||
typename InitMethod,
|
||||
typename ...InitArgs,
|
||||
typename = decltype(std::declval<std::decay_t<InitMethod>>()(
|
||||
std::declval<not_null<GenericBox*>>(),
|
||||
std::declval<std::decay_t<InitArgs>>()...))>
|
||||
GenericBox(
|
||||
QWidget*,
|
||||
InitMethod &&init,
|
||||
InitArgs &&...args);
|
||||
|
||||
void setWidth(int width) {
|
||||
_width = width;
|
||||
}
|
||||
void setFocusCallback(Fn<void()> callback) {
|
||||
_focus = callback;
|
||||
}
|
||||
void setInitScrollCallback(Fn<void()> callback) {
|
||||
_initScroll = callback;
|
||||
}
|
||||
void setShowFinishedCallback(Fn<void()> callback) {
|
||||
_showFinished = callback;
|
||||
}
|
||||
[[nodiscard]] rpl::producer<> showFinishes() const {
|
||||
return _showFinishes.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] int rowsCount() const;
|
||||
[[nodiscard]] int width() const;
|
||||
|
||||
template <
|
||||
typename Widget,
|
||||
typename = std::enable_if_t<
|
||||
std::is_base_of_v<RpWidget, Widget>>>
|
||||
Widget *insertRow(
|
||||
int atPosition,
|
||||
object_ptr<Widget> &&child,
|
||||
const style::margins &margin = st::boxRowPadding,
|
||||
style::align align = style::al_left) {
|
||||
return _content->insert(
|
||||
atPosition,
|
||||
std::move(child),
|
||||
margin,
|
||||
align);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Widget,
|
||||
typename = std::enable_if_t<
|
||||
std::is_base_of_v<RpWidget, Widget>>>
|
||||
Widget *insertRow(
|
||||
int atPosition,
|
||||
object_ptr<Widget> &&child,
|
||||
style::align align) {
|
||||
return _content->insert(
|
||||
atPosition,
|
||||
std::move(child),
|
||||
st::boxRowPadding,
|
||||
align);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Widget,
|
||||
typename = std::enable_if_t<
|
||||
std::is_base_of_v<RpWidget, Widget>>>
|
||||
Widget *addRow(
|
||||
object_ptr<Widget> &&child,
|
||||
const style::margins &margin = st::boxRowPadding,
|
||||
style::align align = style::al_left) {
|
||||
return _content->add(std::move(child), margin, align);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Widget,
|
||||
typename = std::enable_if_t<
|
||||
std::is_base_of_v<RpWidget, Widget>>>
|
||||
Widget *addRow(object_ptr<Widget> &&child, style::align align) {
|
||||
return _content->add(std::move(child), st::boxRowPadding, align);
|
||||
}
|
||||
|
||||
void addSkip(int height);
|
||||
|
||||
void setMaxHeight(int maxHeight) {
|
||||
_maxHeight = maxHeight;
|
||||
}
|
||||
void setMinHeight(int minHeight) {
|
||||
_minHeight = minHeight;
|
||||
}
|
||||
void setScrollStyle(const style::ScrollArea &st) {
|
||||
_scrollSt = &st;
|
||||
}
|
||||
|
||||
void setInnerFocus() override;
|
||||
void showFinished() override;
|
||||
|
||||
template <typename Widget>
|
||||
not_null<Widget*> setPinnedToTopContent(object_ptr<Widget> content) {
|
||||
return static_cast<Widget*>(
|
||||
doSetPinnedToTopContent(std::move(content)).get());
|
||||
}
|
||||
|
||||
template <typename Widget>
|
||||
not_null<Widget*> setPinnedToBottomContent(object_ptr<Widget> content) {
|
||||
return static_cast<Widget*>(
|
||||
doSetPinnedToBottomContent(std::move(content)).get());
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout();
|
||||
|
||||
using BoxContent::setNoContentMargin;
|
||||
|
||||
private:
|
||||
template <typename InitMethod, typename ...InitArgs>
|
||||
struct Initer {
|
||||
template <
|
||||
typename OtherMethod,
|
||||
typename ...OtherArgs,
|
||||
typename = std::enable_if_t<
|
||||
std::is_constructible_v<InitMethod, OtherMethod&&>>>
|
||||
Initer(OtherMethod &&method, OtherArgs &&...args);
|
||||
|
||||
void operator()(not_null<GenericBox*> box);
|
||||
|
||||
template <std::size_t... I>
|
||||
void call(
|
||||
not_null<GenericBox*> box,
|
||||
std::index_sequence<I...>);
|
||||
|
||||
InitMethod method;
|
||||
std::tuple<InitArgs...> args;
|
||||
};
|
||||
|
||||
template <typename InitMethod, typename ...InitArgs>
|
||||
auto MakeIniter(InitMethod &&method, InitArgs &&...args)
|
||||
-> Initer<std::decay_t<InitMethod>, std::decay_t<InitArgs>...>;
|
||||
|
||||
void prepare() override;
|
||||
not_null<Ui::RpWidget*> doSetPinnedToTopContent(
|
||||
object_ptr<Ui::RpWidget> content);
|
||||
not_null<Ui::RpWidget*> doSetPinnedToBottomContent(
|
||||
object_ptr<Ui::RpWidget> content);
|
||||
|
||||
FnMut<void(not_null<GenericBox*>)> _init;
|
||||
Fn<void()> _focus;
|
||||
Fn<void()> _initScroll;
|
||||
Fn<void()> _showFinished;
|
||||
rpl::event_stream<> _showFinishes;
|
||||
object_ptr<Ui::VerticalLayout> _owned;
|
||||
not_null<Ui::VerticalLayout*> _content;
|
||||
const style::ScrollArea *_scrollSt = nullptr;
|
||||
int _width = 0;
|
||||
int _minHeight = 0;
|
||||
int _maxHeight = 0;
|
||||
|
||||
object_ptr<Ui::RpWidget> _pinnedToTopContent = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _pinnedToBottomContent = { nullptr };
|
||||
|
||||
};
|
||||
|
||||
template <typename InitMethod, typename ...InitArgs>
|
||||
template <typename OtherMethod, typename ...OtherArgs, typename>
|
||||
GenericBox::Initer<InitMethod, InitArgs...>::Initer(
|
||||
OtherMethod &&method,
|
||||
OtherArgs &&...args)
|
||||
: method(std::forward<OtherMethod>(method))
|
||||
, args(std::forward<OtherArgs>(args)...) {
|
||||
}
|
||||
|
||||
template <typename InitMethod, typename ...InitArgs>
|
||||
inline void GenericBox::Initer<InitMethod, InitArgs...>::operator()(
|
||||
not_null<GenericBox*> box) {
|
||||
call(box, std::make_index_sequence<sizeof...(InitArgs)>());
|
||||
}
|
||||
|
||||
template <typename InitMethod, typename ...InitArgs>
|
||||
template <std::size_t... I>
|
||||
inline void GenericBox::Initer<InitMethod, InitArgs...>::call(
|
||||
not_null<GenericBox*> box,
|
||||
std::index_sequence<I...>) {
|
||||
std::invoke(method, box, std::get<I>(std::move(args))...);
|
||||
}
|
||||
|
||||
template <typename InitMethod, typename ...InitArgs>
|
||||
inline auto GenericBox::MakeIniter(InitMethod &&method, InitArgs &&...args)
|
||||
-> Initer<std::decay_t<InitMethod>, std::decay_t<InitArgs>...> {
|
||||
return {
|
||||
std::forward<InitMethod>(method),
|
||||
std::forward<InitArgs>(args)...
|
||||
};
|
||||
}
|
||||
|
||||
template <typename InitMethod, typename ...InitArgs, typename>
|
||||
inline GenericBox::GenericBox(
|
||||
QWidget*,
|
||||
InitMethod &&init,
|
||||
InitArgs &&...args)
|
||||
: _init(
|
||||
MakeIniter(
|
||||
std::forward<InitMethod>(init),
|
||||
std::forward<InitArgs>(args)...))
|
||||
, _owned(this)
|
||||
, _content(_owned.data()) {
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<> BoxShowFinishes(not_null<GenericBox*> box);
|
||||
|
||||
} // namespace Ui
|
||||
191
Telegram/lib_ui/ui/layers/layer_manager.cpp
Normal file
191
Telegram/lib_ui/ui/layers/layer_manager.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
// 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/layer_manager.h"
|
||||
|
||||
#include "ui/layers/show.h"
|
||||
#include "ui/ui_utility.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class LayerManager::ManagerShow final : public Show {
|
||||
public:
|
||||
explicit ManagerShow(not_null<LayerManager*> manager);
|
||||
~ManagerShow();
|
||||
|
||||
void showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<BoxContent>,
|
||||
std::unique_ptr<LayerWidget>> &&layer,
|
||||
LayerOptions options,
|
||||
anim::type animated) const override;
|
||||
[[nodiscard]] not_null<QWidget*> toastParent() const override;
|
||||
[[nodiscard]] bool valid() const override;
|
||||
operator bool() const override;
|
||||
|
||||
private:
|
||||
const base::weak_ptr<LayerManager> _manager;
|
||||
|
||||
};
|
||||
|
||||
LayerManager::ManagerShow::ManagerShow(not_null<LayerManager*> manager)
|
||||
: _manager(manager.get()) {
|
||||
}
|
||||
|
||||
LayerManager::ManagerShow::~ManagerShow() = default;
|
||||
|
||||
void LayerManager::ManagerShow::showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<BoxContent>,
|
||||
std::unique_ptr<LayerWidget>> &&layer,
|
||||
LayerOptions options,
|
||||
anim::type animated) const {
|
||||
using UniqueLayer = std::unique_ptr<Ui::LayerWidget>;
|
||||
using ObjectBox = object_ptr<Ui::BoxContent>;
|
||||
if (auto layerWidget = std::get_if<UniqueLayer>(&layer)) {
|
||||
if (const auto manager = _manager.get()) {
|
||||
manager->showLayer(std::move(*layerWidget), options, animated);
|
||||
}
|
||||
} else if (auto box = std::get_if<ObjectBox>(&layer)) {
|
||||
if (const auto manager = _manager.get()) {
|
||||
manager->showBox(std::move(*box), options, animated);
|
||||
}
|
||||
} else if (const auto manager = _manager.get()) {
|
||||
manager->hideAll(animated);
|
||||
}
|
||||
}
|
||||
|
||||
not_null<QWidget*> LayerManager::ManagerShow::toastParent() const {
|
||||
const auto manager = _manager.get();
|
||||
|
||||
Ensures(manager != nullptr);
|
||||
return manager->toastParent();
|
||||
}
|
||||
|
||||
bool LayerManager::ManagerShow::valid() const {
|
||||
return (_manager.get() != nullptr);
|
||||
}
|
||||
|
||||
LayerManager::ManagerShow::operator bool() const {
|
||||
return valid();
|
||||
}
|
||||
|
||||
LayerManager::LayerManager(not_null<RpWidget*> widget)
|
||||
: _widget(widget) {
|
||||
}
|
||||
|
||||
void LayerManager::setStyleOverrides(
|
||||
const style::Box *boxSt,
|
||||
const style::Box *layerSt) {
|
||||
_boxSt = boxSt;
|
||||
_layerSt = layerSt;
|
||||
if (_layer) {
|
||||
_layer->setStyleOverrides(_boxSt, _layerSt);
|
||||
}
|
||||
}
|
||||
|
||||
void LayerManager::setHideByBackgroundClick(bool hide) {
|
||||
_hideByBackgroundClick = hide;
|
||||
if (_layer) {
|
||||
_layer->setHideByBackgroundClick(hide);
|
||||
}
|
||||
}
|
||||
|
||||
void LayerManager::showBox(
|
||||
object_ptr<BoxContent> box,
|
||||
LayerOptions options,
|
||||
anim::type animated) {
|
||||
ensureLayerCreated();
|
||||
_layer->showBox(std::move(box), options, animated);
|
||||
setFocus();
|
||||
}
|
||||
|
||||
void LayerManager::showLayer(
|
||||
std::unique_ptr<LayerWidget> layer,
|
||||
LayerOptions options,
|
||||
anim::type animated) {
|
||||
ensureLayerCreated();
|
||||
_layer->showLayer(std::move(layer), options, animated);
|
||||
setFocus();
|
||||
}
|
||||
|
||||
void LayerManager::hideAll(anim::type animated) {
|
||||
if (animated == anim::type::instant) {
|
||||
destroyLayer();
|
||||
} else if (_layer) {
|
||||
_layer->hideAll(animated);
|
||||
}
|
||||
}
|
||||
|
||||
void LayerManager::raise() {
|
||||
if (_layer) {
|
||||
_layer->raise();
|
||||
}
|
||||
}
|
||||
|
||||
bool LayerManager::setFocus() {
|
||||
if (!_layer) {
|
||||
return false;
|
||||
}
|
||||
_layer->setInnerFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<Show> LayerManager::uiShow() {
|
||||
if (!_cachedShow) {
|
||||
_cachedShow = std::make_shared<ManagerShow>(this);
|
||||
}
|
||||
return _cachedShow;
|
||||
}
|
||||
|
||||
const LayerWidget *LayerManager::topShownLayer() const {
|
||||
return _layer ? _layer->topShownLayer() : nullptr;
|
||||
}
|
||||
|
||||
void LayerManager::ensureLayerCreated() {
|
||||
if (_layer) {
|
||||
return;
|
||||
}
|
||||
_layer.emplace(_widget, crl::guard(this, [=] {
|
||||
return uiShow();
|
||||
}));
|
||||
_layer->setHideByBackgroundClick(_hideByBackgroundClick);
|
||||
_layer->setStyleOverrides(_boxSt, _layerSt);
|
||||
|
||||
_layer->hideFinishEvents(
|
||||
) | rpl::filter([=] {
|
||||
return _layer != nullptr; // Last hide finish is sent from destructor.
|
||||
}) | rpl::on_next([=] {
|
||||
destroyLayer();
|
||||
}, _layer->lifetime());
|
||||
|
||||
_layer->move(0, 0);
|
||||
_widget->sizeValue(
|
||||
) | rpl::on_next([=](QSize size) {
|
||||
_layer->resize(size);
|
||||
}, _layer->lifetime());
|
||||
|
||||
_layerShown = true;
|
||||
}
|
||||
|
||||
void LayerManager::destroyLayer() {
|
||||
if (!_layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto layer = base::take(_layer);
|
||||
_layerShown = false;
|
||||
|
||||
const auto resetFocus = Ui::InFocusChain(layer);
|
||||
if (resetFocus) {
|
||||
_widget->setFocus();
|
||||
}
|
||||
layer = nullptr;
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
73
Telegram/lib_ui/ui/layers/layer_manager.h
Normal file
73
Telegram/lib_ui/ui/layers/layer_manager.h
Normal file
@@ -0,0 +1,73 @@
|
||||
// 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
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/weak_ptr.h"
|
||||
#include "ui/layers/layer_widget.h"
|
||||
|
||||
#include <QtCore/QMargins>
|
||||
|
||||
namespace style {
|
||||
struct Box;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class BoxContent;
|
||||
class RpWidget;
|
||||
class Show;
|
||||
|
||||
class LayerManager final : public base::has_weak_ptr {
|
||||
public:
|
||||
explicit LayerManager(not_null<RpWidget*> widget);
|
||||
|
||||
void setStyleOverrides(
|
||||
const style::Box *boxSt,
|
||||
const style::Box *layerSt);
|
||||
|
||||
void setHideByBackgroundClick(bool hide);
|
||||
void showBox(
|
||||
object_ptr<BoxContent> box,
|
||||
LayerOptions options = LayerOption::KeepOther,
|
||||
anim::type animated = anim::type::normal);
|
||||
void showLayer(
|
||||
std::unique_ptr<LayerWidget> layer,
|
||||
LayerOptions options = LayerOption::KeepOther,
|
||||
anim::type animated = anim::type::normal);
|
||||
void hideAll(anim::type animated = anim::type::normal);
|
||||
void raise();
|
||||
bool setFocus();
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> layerShownValue() const {
|
||||
return _layerShown.value();
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> toastParent() const {
|
||||
return _widget;
|
||||
}
|
||||
[[nodiscard]] const LayerWidget *topShownLayer() const;
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Show> uiShow();
|
||||
|
||||
private:
|
||||
class ManagerShow;
|
||||
|
||||
void ensureLayerCreated();
|
||||
void destroyLayer();
|
||||
|
||||
const not_null<RpWidget*> _widget;
|
||||
base::unique_qptr<LayerStackWidget> _layer;
|
||||
std::shared_ptr<ManagerShow> _cachedShow;
|
||||
rpl::variable<bool> _layerShown;
|
||||
|
||||
const style::Box *_boxSt = nullptr;
|
||||
const style::Box *_layerSt = nullptr;
|
||||
bool _hideByBackgroundClick = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
||||
955
Telegram/lib_ui/ui/layers/layer_widget.cpp
Normal file
955
Telegram/lib_ui/ui/layers/layer_widget.cpp
Normal file
@@ -0,0 +1,955 @@
|
||||
// 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/layer_widget.h"
|
||||
|
||||
#include "ui/cached_special_layer_shadow_corners.h"
|
||||
#include "ui/layers/box_layer_widget.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/round_rect.h"
|
||||
#include "base/qt/qt_tab_key.h"
|
||||
#include "base/integration.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/palette.h"
|
||||
|
||||
#include <QtGui/QtEvents>
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class LayerStackWidget::BackgroundWidget : public RpWidget {
|
||||
public:
|
||||
using RpWidget::RpWidget;
|
||||
|
||||
void setDoneCallback(Fn<void()> callback) {
|
||||
_doneCallback = std::move(callback);
|
||||
}
|
||||
|
||||
void setLayerBoxes(const QRect &specialLayerBox, const QRect &layerBox);
|
||||
void setCacheImages(
|
||||
QPixmap &&bodyCache,
|
||||
QPixmap &&mainMenuCache,
|
||||
QPixmap &&specialLayerCache,
|
||||
QPixmap &&layerCache);
|
||||
void removeBodyCache();
|
||||
[[nodiscard]] bool hasBodyCache() const;
|
||||
void refreshBodyCache(QPixmap &&bodyCache);
|
||||
void startAnimation(Action action);
|
||||
void skipAnimation(Action action);
|
||||
void finishAnimating();
|
||||
|
||||
bool animating() const {
|
||||
return _a_mainMenuShown.animating() || _a_specialLayerShown.animating() || _a_layerShown.animating();
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
bool isShown() const {
|
||||
return _mainMenuShown || _specialLayerShown || _layerShown;
|
||||
}
|
||||
void checkIfDone();
|
||||
void setMainMenuShown(bool shown);
|
||||
void setSpecialLayerShown(bool shown);
|
||||
void setLayerShown(bool shown);
|
||||
void checkWasShown(bool wasShown);
|
||||
void animationCallback();
|
||||
|
||||
QPixmap _bodyCache;
|
||||
QPixmap _mainMenuCache;
|
||||
int _mainMenuCacheWidth = 0;
|
||||
QPixmap _specialLayerCache;
|
||||
QPixmap _layerCache;
|
||||
|
||||
Fn<void()> _doneCallback;
|
||||
|
||||
bool _wasAnimating = false;
|
||||
bool _inPaintEvent = false;
|
||||
bool _repaintIssued = false;
|
||||
Ui::Animations::Simple _a_shown;
|
||||
Ui::Animations::Simple _a_mainMenuShown;
|
||||
Ui::Animations::Simple _a_specialLayerShown;
|
||||
Ui::Animations::Simple _a_layerShown;
|
||||
|
||||
QRect _specialLayerBox, _specialLayerCacheBox;
|
||||
QRect _layerBox, _layerCacheBox;
|
||||
int _mainMenuRight = 0;
|
||||
|
||||
bool _mainMenuShown = false;
|
||||
bool _specialLayerShown = false;
|
||||
bool _layerShown = false;
|
||||
|
||||
};
|
||||
|
||||
void LayerStackWidget::BackgroundWidget::setCacheImages(
|
||||
QPixmap &&bodyCache,
|
||||
QPixmap &&mainMenuCache,
|
||||
QPixmap &&specialLayerCache,
|
||||
QPixmap &&layerCache) {
|
||||
_bodyCache = std::move(bodyCache);
|
||||
_mainMenuCache = std::move(mainMenuCache);
|
||||
_specialLayerCache = std::move(specialLayerCache);
|
||||
_layerCache = std::move(layerCache);
|
||||
_specialLayerCacheBox = _specialLayerBox;
|
||||
_layerCacheBox = _layerBox;
|
||||
_repaintIssued = false;
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, !_bodyCache.isNull());
|
||||
}
|
||||
|
||||
void LayerStackWidget::BackgroundWidget::removeBodyCache() {
|
||||
if (hasBodyCache()) {
|
||||
_bodyCache = {};
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
||||
}
|
||||
}
|
||||
|
||||
bool LayerStackWidget::BackgroundWidget::hasBodyCache() const {
|
||||
return !_bodyCache.isNull();
|
||||
}
|
||||
|
||||
void LayerStackWidget::BackgroundWidget::refreshBodyCache(
|
||||
QPixmap &&bodyCache) {
|
||||
_bodyCache = std::move(bodyCache);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, !_bodyCache.isNull());
|
||||
}
|
||||
|
||||
void LayerStackWidget::BackgroundWidget::startAnimation(Action action) {
|
||||
if (action == Action::ShowMainMenu) {
|
||||
setMainMenuShown(true);
|
||||
} else if (action != Action::HideLayer
|
||||
&& action != Action::HideSpecialLayer) {
|
||||
setMainMenuShown(false);
|
||||
}
|
||||
if (action == Action::ShowSpecialLayer) {
|
||||
setSpecialLayerShown(true);
|
||||
} else if (action == Action::ShowMainMenu
|
||||
|| action == Action::HideAll
|
||||
|| action == Action::HideSpecialLayer) {
|
||||
setSpecialLayerShown(false);
|
||||
}
|
||||
if (action == Action::ShowLayer) {
|
||||
setLayerShown(true);
|
||||
} else if (action != Action::ShowSpecialLayer
|
||||
&& action != Action::HideSpecialLayer) {
|
||||
setLayerShown(false);
|
||||
}
|
||||
_wasAnimating = true;
|
||||
checkIfDone();
|
||||
}
|
||||
|
||||
void LayerStackWidget::BackgroundWidget::skipAnimation(Action action) {
|
||||
_repaintIssued = false;
|
||||
startAnimation(action);
|
||||
finishAnimating();
|
||||
}
|
||||
|
||||
void LayerStackWidget::BackgroundWidget::checkIfDone() {
|
||||
if (!_wasAnimating || _inPaintEvent || animating()) {
|
||||
return;
|
||||
}
|
||||
_wasAnimating = false;
|
||||
_mainMenuCache = _specialLayerCache = _layerCache = QPixmap();
|
||||
removeBodyCache();
|
||||
if (_doneCallback) {
|
||||
_doneCallback();
|
||||
}
|
||||
}
|
||||
|
||||
void LayerStackWidget::BackgroundWidget::setMainMenuShown(bool shown) {
|
||||
auto wasShown = isShown();
|
||||
if (_mainMenuShown != shown) {
|
||||
_mainMenuShown = shown;
|
||||
_a_mainMenuShown.start([this] { animationCallback(); }, _mainMenuShown ? 0. : 1., _mainMenuShown ? 1. : 0., st::boxDuration, anim::easeOutCirc);
|
||||
}
|
||||
_mainMenuCacheWidth = (_mainMenuCache.width() / style::DevicePixelRatio())
|
||||
- st::boxRoundShadow.extend.right();
|
||||
_mainMenuRight = _mainMenuShown ? _mainMenuCacheWidth : 0;
|
||||
checkWasShown(wasShown);
|
||||
}
|
||||
|
||||
void LayerStackWidget::BackgroundWidget::setSpecialLayerShown(bool shown) {
|
||||
auto wasShown = isShown();
|
||||
if (_specialLayerShown != shown) {
|
||||
_specialLayerShown = shown;
|
||||
_a_specialLayerShown.start([this] { animationCallback(); }, _specialLayerShown ? 0. : 1., _specialLayerShown ? 1. : 0., st::boxDuration);
|
||||
}
|
||||
checkWasShown(wasShown);
|
||||
}
|
||||
|
||||
void LayerStackWidget::BackgroundWidget::setLayerShown(bool shown) {
|
||||
auto wasShown = isShown();
|
||||
if (_layerShown != shown) {
|
||||
_layerShown = shown;
|
||||
_a_layerShown.start([this] { animationCallback(); }, _layerShown ? 0. : 1., _layerShown ? 1. : 0., st::boxDuration);
|
||||
}
|
||||
checkWasShown(wasShown);
|
||||
}
|
||||
|
||||
void LayerStackWidget::BackgroundWidget::checkWasShown(bool wasShown) {
|
||||
if (isShown() != wasShown) {
|
||||
_a_shown.start([this] { animationCallback(); }, wasShown ? 1. : 0., wasShown ? 0. : 1., st::boxDuration, anim::easeOutCirc);
|
||||
}
|
||||
}
|
||||
|
||||
void LayerStackWidget::BackgroundWidget::setLayerBoxes(const QRect &specialLayerBox, const QRect &layerBox) {
|
||||
_specialLayerBox = specialLayerBox;
|
||||
_layerBox = layerBox;
|
||||
update();
|
||||
}
|
||||
|
||||
void LayerStackWidget::BackgroundWidget::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
_inPaintEvent = true;
|
||||
auto guard = gsl::finally([this] {
|
||||
_inPaintEvent = false;
|
||||
crl::on_main(this, [=] { checkIfDone(); });
|
||||
});
|
||||
|
||||
if (!_bodyCache.isNull()) {
|
||||
p.drawPixmap(0, 0, _bodyCache);
|
||||
}
|
||||
|
||||
auto specialLayerBox = _specialLayerCache.isNull() ? _specialLayerBox : _specialLayerCacheBox;
|
||||
auto layerBox = _layerCache.isNull() ? _layerBox : _layerCacheBox;
|
||||
|
||||
auto mainMenuProgress = _a_mainMenuShown.value(-1);
|
||||
auto mainMenuRight = (_mainMenuCache.isNull() || mainMenuProgress < 0) ? _mainMenuRight : (mainMenuProgress < 0) ? _mainMenuRight : anim::interpolate(0, _mainMenuCacheWidth, mainMenuProgress);
|
||||
if (mainMenuRight) {
|
||||
// Move showing boxes to the right while main menu is hiding.
|
||||
if (!_specialLayerCache.isNull()) {
|
||||
specialLayerBox.moveLeft(specialLayerBox.left() + mainMenuRight / 2);
|
||||
}
|
||||
if (!_layerCache.isNull()) {
|
||||
layerBox.moveLeft(layerBox.left() + mainMenuRight / 2);
|
||||
}
|
||||
}
|
||||
auto bgOpacity = _a_shown.value(isShown() ? 1. : 0.);
|
||||
auto specialLayerOpacity = _a_specialLayerShown.value(_specialLayerShown ? 1. : 0.);
|
||||
auto layerOpacity = _a_layerShown.value(_layerShown ? 1. : 0.);
|
||||
if (bgOpacity == 0.) {
|
||||
return;
|
||||
}
|
||||
|
||||
p.setOpacity(bgOpacity);
|
||||
auto overSpecialOpacity = (layerOpacity * specialLayerOpacity);
|
||||
auto bg = myrtlrect(mainMenuRight, 0, width() - mainMenuRight, height());
|
||||
|
||||
if (_mainMenuCache.isNull() && mainMenuRight > 0) {
|
||||
// All cache images are taken together with their shadows,
|
||||
// so we paint shadow only when there is no cache.
|
||||
Ui::Shadow::paint(p, myrtlrect(0, 0, mainMenuRight, height()), width(), st::boxRoundShadow, RectPart::Right);
|
||||
}
|
||||
|
||||
if (_specialLayerCache.isNull() && !specialLayerBox.isEmpty()) {
|
||||
// All cache images are taken together with their shadows,
|
||||
// so we paint shadow only when there is no cache.
|
||||
auto sides = RectPart::Left | RectPart::Right;
|
||||
auto topCorners = (specialLayerBox.y() > 0);
|
||||
auto bottomCorners = (specialLayerBox.y() + specialLayerBox.height() < height());
|
||||
if (topCorners) {
|
||||
sides |= RectPart::Top;
|
||||
}
|
||||
if (bottomCorners) {
|
||||
sides |= RectPart::Bottom;
|
||||
}
|
||||
if (topCorners || bottomCorners) {
|
||||
p.setClipRegion(QRegion(rect()) - specialLayerBox.marginsRemoved(QMargins(st::boxRadius, 0, st::boxRadius, 0)) - specialLayerBox.marginsRemoved(QMargins(0, st::boxRadius, 0, st::boxRadius)));
|
||||
}
|
||||
Ui::Shadow::paint(p, specialLayerBox, width(), st::boxRoundShadow, Ui::SpecialLayerShadowCorners(), sides);
|
||||
if (topCorners || bottomCorners) {
|
||||
p.setClipping(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!layerBox.isEmpty() && !_specialLayerCache.isNull() && overSpecialOpacity < bgOpacity) {
|
||||
// In case of moving special layer below the background while showing a box
|
||||
// we need to fill special layer rect below its cache with a complex opacity
|
||||
// (alpha_final - alpha_current) / (1 - alpha_current) so we won't get glitches
|
||||
// in the transparent special layer cache corners after filling special layer
|
||||
// rect above its cache with alpha_current opacity.
|
||||
const auto region = QRegion(bg) - specialLayerBox;
|
||||
for (const auto &rect : region) {
|
||||
p.fillRect(rect, st::layerBg);
|
||||
}
|
||||
p.setOpacity((bgOpacity - overSpecialOpacity) / (1. - (overSpecialOpacity * st::layerBg->c.alphaF())));
|
||||
p.fillRect(specialLayerBox, st::layerBg);
|
||||
p.setOpacity(bgOpacity);
|
||||
} else {
|
||||
p.fillRect(bg, st::layerBg);
|
||||
}
|
||||
|
||||
if (!_specialLayerCache.isNull() && specialLayerOpacity > 0) {
|
||||
p.setOpacity(specialLayerOpacity);
|
||||
auto cacheLeft = specialLayerBox.x() - st::boxRoundShadow.extend.left();
|
||||
auto cacheTop = specialLayerBox.y() - (specialLayerBox.y() > 0 ? st::boxRoundShadow.extend.top() : 0);
|
||||
p.drawPixmapLeft(cacheLeft, cacheTop, width(), _specialLayerCache);
|
||||
}
|
||||
if (!layerBox.isEmpty()) {
|
||||
if (!_specialLayerCache.isNull()) {
|
||||
p.setOpacity(overSpecialOpacity);
|
||||
p.fillRect(specialLayerBox, st::layerBg);
|
||||
}
|
||||
if (_layerCache.isNull()) {
|
||||
p.setOpacity(layerOpacity);
|
||||
Ui::Shadow::paint(p, layerBox, width(), st::boxRoundShadow);
|
||||
}
|
||||
}
|
||||
if (!_layerCache.isNull() && layerOpacity > 0) {
|
||||
p.setOpacity(layerOpacity);
|
||||
p.drawPixmapLeft(layerBox.topLeft() - QPoint(st::boxRoundShadow.extend.left(), st::boxRoundShadow.extend.top()), width(), _layerCache);
|
||||
}
|
||||
if (!_mainMenuCache.isNull() && mainMenuRight > 0) {
|
||||
p.setOpacity(1.);
|
||||
auto shownWidth = mainMenuRight + st::boxRoundShadow.extend.right();
|
||||
auto sourceWidth = shownWidth * style::DevicePixelRatio();
|
||||
auto sourceRect = style::rtlrect(_mainMenuCache.width() - sourceWidth, 0, sourceWidth, _mainMenuCache.height(), _mainMenuCache.width());
|
||||
p.drawPixmapLeft(0, 0, shownWidth, height(), width(), _mainMenuCache, sourceRect);
|
||||
}
|
||||
if (!_repaintIssued && !_a_shown.animating()) {
|
||||
_repaintIssued = true;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void LayerStackWidget::BackgroundWidget::finishAnimating() {
|
||||
_a_shown.stop();
|
||||
_a_mainMenuShown.stop();
|
||||
_a_specialLayerShown.stop();
|
||||
_a_layerShown.stop();
|
||||
checkIfDone();
|
||||
}
|
||||
|
||||
void LayerStackWidget::BackgroundWidget::animationCallback() {
|
||||
update();
|
||||
checkIfDone();
|
||||
}
|
||||
|
||||
LayerStackWidget::LayerStackWidget(QWidget *parent, ShowFactory showFactory)
|
||||
: RpWidget(parent)
|
||||
, _background(this)
|
||||
, _showFactory(std::move(showFactory)) {
|
||||
setGeometry(parentWidget()->rect());
|
||||
hide();
|
||||
_background->setDoneCallback([this] { animationDone(); });
|
||||
}
|
||||
|
||||
void LayerWidget::setInnerFocus() {
|
||||
if (!isAncestorOf(window()->focusWidget())) {
|
||||
doSetInnerFocus();
|
||||
}
|
||||
}
|
||||
|
||||
bool LayerWidget::overlaps(const QRect &globalRect) {
|
||||
if (isHidden()) {
|
||||
return false;
|
||||
}
|
||||
auto testRect = QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size());
|
||||
if (testAttribute(Qt::WA_OpaquePaintEvent)) {
|
||||
return rect().contains(testRect);
|
||||
}
|
||||
if (QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius).contains(testRect)) {
|
||||
return true;
|
||||
}
|
||||
if (QRect(st::boxRadius, 0, width() - 2 * st::boxRadius, height()).contains(testRect)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LayerWidget::mousePressEvent(QMouseEvent *e) {
|
||||
e->accept();
|
||||
}
|
||||
|
||||
void LayerWidget::resizeEvent(QResizeEvent *e) {
|
||||
if (_resizedCallback) {
|
||||
_resizedCallback();
|
||||
}
|
||||
}
|
||||
|
||||
bool LayerWidget::focusNextPrevChild(bool next) {
|
||||
return base::FocusNextPrevChildBlocked(this, next);
|
||||
}
|
||||
|
||||
void LayerStackWidget::setHideByBackgroundClick(bool hide) {
|
||||
_hideByBackgroundClick = hide;
|
||||
}
|
||||
|
||||
void LayerStackWidget::keyPressEvent(QKeyEvent *e) {
|
||||
if (e->key() == Qt::Key_Escape) {
|
||||
hideCurrent(anim::type::normal);
|
||||
}
|
||||
}
|
||||
|
||||
void LayerStackWidget::mousePressEvent(QMouseEvent *e) {
|
||||
Ui::PostponeCall(this, [=] { backgroundClicked(); });
|
||||
}
|
||||
|
||||
void LayerStackWidget::backgroundClicked() {
|
||||
if (!_hideByBackgroundClick) {
|
||||
return;
|
||||
}
|
||||
if (const auto layer = currentLayer()) {
|
||||
if (!layer->closeByOutsideClick()) {
|
||||
return;
|
||||
}
|
||||
} else if (const auto special = _specialLayer.data()) {
|
||||
if (!special->closeByOutsideClick()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
hideCurrent(anim::type::normal);
|
||||
}
|
||||
|
||||
void LayerStackWidget::hideCurrent(anim::type animated) {
|
||||
return currentLayer() ? hideLayers(animated) : hideAll(animated);
|
||||
}
|
||||
|
||||
void LayerStackWidget::hideLayers(anim::type animated) {
|
||||
startAnimation([] {}, [&] {
|
||||
clearLayers();
|
||||
}, Action::HideLayer, animated);
|
||||
}
|
||||
|
||||
void LayerStackWidget::hideAll(anim::type animated) {
|
||||
startAnimation([] {}, [&] {
|
||||
clearLayers();
|
||||
clearSpecialLayer();
|
||||
_mainMenu.destroy();
|
||||
}, Action::HideAll, animated);
|
||||
}
|
||||
|
||||
void LayerStackWidget::hideAllAnimatedPrepare() {
|
||||
prepareAnimation([] {}, [&] {
|
||||
clearLayers();
|
||||
clearSpecialLayer();
|
||||
_mainMenu.destroy();
|
||||
}, Action::HideAll, anim::type::normal);
|
||||
}
|
||||
|
||||
void LayerStackWidget::hideAllAnimatedRun() {
|
||||
if (_background->hasBodyCache()) {
|
||||
removeBodyCache();
|
||||
hideChildren();
|
||||
auto bodyCache = Ui::GrabWidget(parentWidget());
|
||||
showChildren();
|
||||
_background->refreshBodyCache(std::move(bodyCache));
|
||||
}
|
||||
_background->startAnimation(Action::HideAll);
|
||||
}
|
||||
|
||||
void LayerStackWidget::hideTopLayer(anim::type animated) {
|
||||
if (_specialLayer || _mainMenu) {
|
||||
hideLayers(animated);
|
||||
} else {
|
||||
hideAll(animated);
|
||||
}
|
||||
}
|
||||
|
||||
void LayerStackWidget::removeBodyCache() {
|
||||
_background->removeBodyCache();
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
||||
}
|
||||
|
||||
bool LayerStackWidget::layerShown() const {
|
||||
return _specialLayer || currentLayer() || _mainMenu;
|
||||
}
|
||||
|
||||
const LayerWidget *LayerStackWidget::topShownLayer() const {
|
||||
if (const auto result = currentLayer()) {
|
||||
return result;
|
||||
} else if (const auto special = _specialLayer.data()) {
|
||||
return special;
|
||||
} else if (const auto menu = _mainMenu.data()) {
|
||||
return menu;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void LayerStackWidget::setStyleOverrides(
|
||||
const style::Box *boxSt,
|
||||
const style::Box *layerSt) {
|
||||
_boxSt = boxSt;
|
||||
_layerSt = layerSt;
|
||||
}
|
||||
|
||||
void LayerStackWidget::setCacheImages() {
|
||||
auto bodyCache = QPixmap(), mainMenuCache = QPixmap();
|
||||
auto specialLayerCache = QPixmap();
|
||||
if (_specialLayer) {
|
||||
Ui::SendPendingMoveResizeEvents(_specialLayer);
|
||||
auto sides = RectPart::Left | RectPart::Right;
|
||||
if (_specialLayer->y() > 0) {
|
||||
sides |= RectPart::Top;
|
||||
}
|
||||
if (_specialLayer->y() + _specialLayer->height() < height()) {
|
||||
sides |= RectPart::Bottom;
|
||||
}
|
||||
specialLayerCache = Ui::Shadow::grab(_specialLayer, st::boxRoundShadow, sides);
|
||||
}
|
||||
auto layerCache = QPixmap();
|
||||
if (auto layer = currentLayer()) {
|
||||
layerCache = Ui::Shadow::grab(layer, st::boxRoundShadow);
|
||||
}
|
||||
if (isAncestorOf(window()->focusWidget())) {
|
||||
setFocus();
|
||||
}
|
||||
if (_mainMenu) {
|
||||
removeBodyCache();
|
||||
hideChildren();
|
||||
bodyCache = Ui::GrabWidget(parentWidget());
|
||||
showChildren();
|
||||
mainMenuCache = Ui::Shadow::grab(_mainMenu, st::boxRoundShadow, RectPart::Right);
|
||||
}
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, !bodyCache.isNull());
|
||||
updateLayerBoxes();
|
||||
_background->setCacheImages(std::move(bodyCache), std::move(mainMenuCache), std::move(specialLayerCache), std::move(layerCache));
|
||||
}
|
||||
|
||||
void LayerStackWidget::closeLayer(not_null<LayerWidget*> layer) {
|
||||
const auto weak = base::make_weak(layer.get());
|
||||
if (Ui::InFocusChain(layer)) {
|
||||
setFocus();
|
||||
}
|
||||
if (!layer->setClosing()) {
|
||||
// This layer is already closing.
|
||||
return;
|
||||
} else if (!weak) {
|
||||
// setClosing() could've killed the layer.
|
||||
return;
|
||||
}
|
||||
|
||||
if (layer == _specialLayer || layer == _mainMenu) {
|
||||
hideAll(anim::type::normal);
|
||||
} else if (layer == currentLayer()) {
|
||||
if (_layers.size() == 1) {
|
||||
hideCurrent(anim::type::normal);
|
||||
} else {
|
||||
const auto taken = std::move(_layers.back());
|
||||
_layers.pop_back();
|
||||
|
||||
layer = currentLayer();
|
||||
layer->parentResized();
|
||||
if (!_background->animating()) {
|
||||
layer->show();
|
||||
showFinished();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto i = _layers.begin(), e = _layers.end(); i != e; ++i) {
|
||||
if (layer == i->get()) {
|
||||
const auto taken = std::move(*i);
|
||||
_layers.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LayerStackWidget::updateLayerBoxes() {
|
||||
const auto layerBox = [&] {
|
||||
if (const auto layer = currentLayer()) {
|
||||
return layer->geometry();
|
||||
}
|
||||
return QRect();
|
||||
}();
|
||||
const auto specialLayerBox = _specialLayer
|
||||
? _specialLayer->geometry()
|
||||
: QRect();
|
||||
_background->setLayerBoxes(specialLayerBox, layerBox);
|
||||
update();
|
||||
}
|
||||
|
||||
void LayerStackWidget::finishAnimating() {
|
||||
_background->finishAnimating();
|
||||
}
|
||||
|
||||
bool LayerStackWidget::canSetFocus() const {
|
||||
return (currentLayer() || _specialLayer || _mainMenu);
|
||||
}
|
||||
|
||||
void LayerStackWidget::setInnerFocus() {
|
||||
if (_background->animating()) {
|
||||
setFocus();
|
||||
} else if (auto l = currentLayer()) {
|
||||
l->setInnerFocus();
|
||||
} else if (_specialLayer) {
|
||||
_specialLayer->setInnerFocus();
|
||||
} else if (_mainMenu) {
|
||||
_mainMenu->setInnerFocus();
|
||||
}
|
||||
}
|
||||
|
||||
bool LayerStackWidget::contentOverlapped(const QRect &globalRect) {
|
||||
if (isHidden()) {
|
||||
return false;
|
||||
}
|
||||
if (_specialLayer && _specialLayer->overlaps(globalRect)) {
|
||||
return true;
|
||||
}
|
||||
if (auto layer = currentLayer()) {
|
||||
return layer->overlaps(globalRect);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename SetupNew, typename ClearOld>
|
||||
bool LayerStackWidget::prepareAnimation(
|
||||
SetupNew &&setupNewWidgets,
|
||||
ClearOld &&clearOldWidgets,
|
||||
Action action,
|
||||
anim::type animated) {
|
||||
if (animated == anim::type::instant) {
|
||||
setupNewWidgets();
|
||||
clearOldWidgets();
|
||||
prepareForAnimation();
|
||||
_background->skipAnimation(action);
|
||||
} else {
|
||||
setupNewWidgets();
|
||||
setCacheImages();
|
||||
const auto weak = base::make_weak(this);
|
||||
clearOldWidgets();
|
||||
if (weak) {
|
||||
prepareForAnimation();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename SetupNew, typename ClearOld>
|
||||
void LayerStackWidget::startAnimation(
|
||||
SetupNew &&setupNewWidgets,
|
||||
ClearOld &&clearOldWidgets,
|
||||
Action action,
|
||||
anim::type animated) {
|
||||
const auto alive = prepareAnimation(
|
||||
std::forward<SetupNew>(setupNewWidgets),
|
||||
std::forward<ClearOld>(clearOldWidgets),
|
||||
action,
|
||||
animated);
|
||||
if (alive) {
|
||||
_background->startAnimation(action);
|
||||
}
|
||||
}
|
||||
|
||||
void LayerStackWidget::resizeEvent(QResizeEvent *e) {
|
||||
const auto weak = base::make_weak(this);
|
||||
_background->setGeometry(rect());
|
||||
if (!weak) {
|
||||
return;
|
||||
}
|
||||
if (_specialLayer) {
|
||||
_specialLayer->parentResized();
|
||||
if (!weak) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (const auto layer = currentLayer()) {
|
||||
layer->parentResized();
|
||||
if (!weak) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_mainMenu) {
|
||||
_mainMenu->parentResized();
|
||||
if (!weak) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateLayerBoxes();
|
||||
}
|
||||
|
||||
void LayerStackWidget::prepareForAnimation() {
|
||||
if (isHidden()) {
|
||||
show();
|
||||
}
|
||||
if (_mainMenu) {
|
||||
if (Ui::InFocusChain(_mainMenu)) {
|
||||
setFocus();
|
||||
}
|
||||
_mainMenu->hide();
|
||||
}
|
||||
if (_specialLayer) {
|
||||
if (Ui::InFocusChain(_specialLayer)) {
|
||||
setFocus();
|
||||
}
|
||||
_specialLayer->hide();
|
||||
}
|
||||
if (const auto layer = currentLayer()) {
|
||||
if (Ui::InFocusChain(layer)) {
|
||||
setFocus();
|
||||
}
|
||||
layer->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void LayerStackWidget::animationDone() {
|
||||
auto &integration = base::Integration::Instance();
|
||||
bool hidden = true;
|
||||
if (_mainMenu) {
|
||||
integration.setCrashAnnotation("ShowingWidget", u"MainMenu"_q);
|
||||
_mainMenu->show();
|
||||
hidden = false;
|
||||
}
|
||||
if (_specialLayer) {
|
||||
integration.setCrashAnnotation("ShowingWidget", u"SpecialLayer"_q);
|
||||
_specialLayer->show();
|
||||
hidden = false;
|
||||
}
|
||||
if (auto layer = currentLayer()) {
|
||||
integration.setCrashAnnotation("ShowingWidget", u"Box"_q);
|
||||
layer->show();
|
||||
hidden = false;
|
||||
}
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
||||
if (hidden) {
|
||||
_hideFinishStream.fire({});
|
||||
} else {
|
||||
integration.setCrashAnnotation("ShowingWidget", u"Finished"_q);
|
||||
showFinished();
|
||||
integration.setCrashAnnotation("ShowingWidget", QString());
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> LayerStackWidget::hideFinishEvents() const {
|
||||
return _hideFinishStream.events();
|
||||
}
|
||||
|
||||
void LayerStackWidget::showFinished() {
|
||||
fixOrder();
|
||||
sendFakeMouseEvent();
|
||||
updateLayerBoxes();
|
||||
if (_specialLayer) {
|
||||
_specialLayer->showFinished();
|
||||
}
|
||||
if (_mainMenu) {
|
||||
_mainMenu->showFinished();
|
||||
}
|
||||
if (auto layer = currentLayer()) {
|
||||
layer->showFinished();
|
||||
}
|
||||
if (canSetFocus()) {
|
||||
setInnerFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void LayerStackWidget::showSpecialLayer(
|
||||
object_ptr<LayerWidget> layer,
|
||||
anim::type animated) {
|
||||
startAnimation([&] {
|
||||
_specialLayer.destroy();
|
||||
_specialLayer = std::move(layer);
|
||||
initChildLayer(_specialLayer);
|
||||
}, [&] {
|
||||
_mainMenu.destroy();
|
||||
}, Action::ShowSpecialLayer, animated);
|
||||
}
|
||||
|
||||
bool LayerStackWidget::showSectionInternal(
|
||||
not_null<::Window::SectionMemento*> memento,
|
||||
const ::Window::SectionShow ¶ms) {
|
||||
if (_specialLayer) {
|
||||
return _specialLayer->showSectionInternal(memento, params);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LayerStackWidget::hideSpecialLayer(anim::type animated) {
|
||||
startAnimation([] {}, [&] {
|
||||
clearSpecialLayer();
|
||||
_mainMenu.destroy();
|
||||
}, Action::HideSpecialLayer, animated);
|
||||
}
|
||||
|
||||
void LayerStackWidget::showMainMenu(
|
||||
object_ptr<LayerWidget> layer,
|
||||
anim::type animated) {
|
||||
startAnimation([&] {
|
||||
_mainMenu = std::move(layer);
|
||||
initChildLayer(_mainMenu);
|
||||
_mainMenu->moveToLeft(0, 0);
|
||||
}, [&] {
|
||||
clearLayers();
|
||||
_specialLayer.destroy();
|
||||
}, Action::ShowMainMenu, animated);
|
||||
}
|
||||
|
||||
void LayerStackWidget::showBox(
|
||||
object_ptr<BoxContent> box,
|
||||
LayerOptions options,
|
||||
anim::type animated) {
|
||||
showLayer(
|
||||
std::make_unique<BoxLayerWidget>(this, std::move(box)),
|
||||
options,
|
||||
animated);
|
||||
}
|
||||
|
||||
void LayerStackWidget::showLayer(
|
||||
std::unique_ptr<LayerWidget> layer,
|
||||
LayerOptions options,
|
||||
anim::type animated) {
|
||||
if (options & LayerOption::KeepOther) {
|
||||
if (options & LayerOption::ShowAfterOther) {
|
||||
prependLayer(std::move(layer), animated);
|
||||
} else {
|
||||
appendLayer(std::move(layer), animated);
|
||||
}
|
||||
} else {
|
||||
replaceLayer(std::move(layer), animated);
|
||||
}
|
||||
}
|
||||
|
||||
LayerWidget *LayerStackWidget::pushLayer(
|
||||
std::unique_ptr<LayerWidget> layer,
|
||||
anim::type animated) {
|
||||
const auto oldLayer = currentLayer();
|
||||
if (oldLayer) {
|
||||
if (Ui::InFocusChain(oldLayer)) {
|
||||
setFocus();
|
||||
}
|
||||
oldLayer->hide();
|
||||
}
|
||||
_layers.push_back(std::move(layer));
|
||||
const auto raw = _layers.back().get();
|
||||
initChildLayer(raw);
|
||||
|
||||
if (_layers.size() > 1) {
|
||||
if (!_background->animating()) {
|
||||
raw->setVisible(true);
|
||||
showFinished();
|
||||
}
|
||||
} else {
|
||||
startAnimation([] {}, [&] {
|
||||
_mainMenu.destroy();
|
||||
}, Action::ShowLayer, animated);
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
void LayerStackWidget::appendLayer(
|
||||
std::unique_ptr<LayerWidget> layer,
|
||||
anim::type animated) {
|
||||
pushLayer(std::move(layer), animated);
|
||||
}
|
||||
|
||||
void LayerStackWidget::prependLayer(
|
||||
std::unique_ptr<LayerWidget> layer,
|
||||
anim::type animated) {
|
||||
if (_layers.empty()) {
|
||||
replaceLayer(std::move(layer), animated);
|
||||
return;
|
||||
}
|
||||
_layers.insert(
|
||||
begin(_layers),
|
||||
std::move(layer));
|
||||
const auto raw = _layers.front().get();
|
||||
raw->hide();
|
||||
initChildLayer(raw);
|
||||
}
|
||||
|
||||
void LayerStackWidget::replaceLayer(
|
||||
std::unique_ptr<LayerWidget> layer,
|
||||
anim::type animated) {
|
||||
const auto pointer = pushLayer(std::move(layer), animated);
|
||||
const auto removeTill = ranges::find(
|
||||
_layers,
|
||||
pointer,
|
||||
&std::unique_ptr<LayerWidget>::get);
|
||||
_closingLayers.insert(
|
||||
end(_closingLayers),
|
||||
std::make_move_iterator(begin(_layers)),
|
||||
std::make_move_iterator(removeTill));
|
||||
_layers.erase(begin(_layers), removeTill);
|
||||
clearClosingLayers();
|
||||
}
|
||||
|
||||
bool LayerStackWidget::takeToThirdSection() {
|
||||
return _specialLayer
|
||||
? _specialLayer->takeToThirdSection()
|
||||
: false;
|
||||
}
|
||||
|
||||
void LayerStackWidget::clearLayers() {
|
||||
_closingLayers.insert(
|
||||
end(_closingLayers),
|
||||
std::make_move_iterator(begin(_layers)),
|
||||
std::make_move_iterator(end(_layers)));
|
||||
_layers.clear();
|
||||
clearClosingLayers();
|
||||
}
|
||||
|
||||
void LayerStackWidget::clearClosingLayers() {
|
||||
const auto weak = base::make_weak(this);
|
||||
while (!_closingLayers.empty()) {
|
||||
const auto index = _closingLayers.size() - 1;
|
||||
const auto layer = _closingLayers.back().get();
|
||||
if (Ui::InFocusChain(layer)) {
|
||||
setFocus();
|
||||
}
|
||||
|
||||
// This may destroy LayerStackWidget (by calling Ui::hideLayer).
|
||||
// So each time we check a weak pointer (if we are still alive).
|
||||
layer->setClosing();
|
||||
|
||||
// setClosing() could destroy 'this' or could call clearLayers().
|
||||
if (weak && !_closingLayers.empty()) {
|
||||
// We could enqueue more closing layers, so we remove by index.
|
||||
Assert(index < _closingLayers.size());
|
||||
Assert(_closingLayers[index].get() == layer);
|
||||
_closingLayers.erase(begin(_closingLayers) + index);
|
||||
} else {
|
||||
// Everything was destroyed in clearLayers or ~LayerStackWidget.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LayerStackWidget::clearSpecialLayer() {
|
||||
if (_specialLayer) {
|
||||
_specialLayer->setClosing();
|
||||
_specialLayer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void LayerStackWidget::initChildLayer(LayerWidget *layer) {
|
||||
layer->setParent(this);
|
||||
layer->setClosedCallback([=] { closeLayer(layer); });
|
||||
layer->setResizedCallback([=] { updateLayerBoxes(); });
|
||||
Ui::SendPendingMoveResizeEvents(layer);
|
||||
layer->parentResized();
|
||||
}
|
||||
|
||||
void LayerStackWidget::fixOrder() {
|
||||
if (const auto layer = currentLayer()) {
|
||||
_background->raise();
|
||||
layer->raise();
|
||||
} else if (_specialLayer) {
|
||||
_specialLayer->raise();
|
||||
}
|
||||
if (_mainMenu) {
|
||||
_mainMenu->raise();
|
||||
}
|
||||
}
|
||||
|
||||
void LayerStackWidget::sendFakeMouseEvent() {
|
||||
SendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton);
|
||||
}
|
||||
|
||||
LayerStackWidget::~LayerStackWidget() {
|
||||
// Some layer destructors call back into LayerStackWidget.
|
||||
while (!_layers.empty() || !_closingLayers.empty()) {
|
||||
hideAll(anim::type::instant);
|
||||
clearClosingLayers();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
244
Telegram/lib_ui/ui/layers/layer_widget.h
Normal file
244
Telegram/lib_ui/ui/layers/layer_widget.h
Normal file
@@ -0,0 +1,244 @@
|
||||
// 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
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "base/flags.h"
|
||||
|
||||
namespace Window {
|
||||
class SectionMemento;
|
||||
struct SectionShow;
|
||||
} // namespace Window
|
||||
|
||||
namespace style {
|
||||
struct Box;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class BoxContent;
|
||||
|
||||
enum class LayerOption {
|
||||
CloseOther = (1 << 0),
|
||||
KeepOther = (1 << 1),
|
||||
ShowAfterOther = (1 << 2),
|
||||
};
|
||||
using LayerOptions = base::flags<LayerOption>;
|
||||
inline constexpr auto is_flag_type(LayerOption) { return true; };
|
||||
|
||||
class Show;
|
||||
using ShowPtr = std::shared_ptr<Show>;
|
||||
using ShowFactory = Fn<ShowPtr()>;
|
||||
|
||||
class LayerWidget : public RpWidget {
|
||||
public:
|
||||
using RpWidget::RpWidget;
|
||||
|
||||
virtual void parentResized() = 0;
|
||||
virtual void showFinished() {
|
||||
}
|
||||
void setInnerFocus();
|
||||
bool setClosing() {
|
||||
if (!_closing) {
|
||||
_closing = true;
|
||||
closeHook();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool overlaps(const QRect &globalRect);
|
||||
|
||||
void setClosedCallback(Fn<void()> callback) {
|
||||
_closedCallback = std::move(callback);
|
||||
}
|
||||
void setResizedCallback(Fn<void()> callback) {
|
||||
_resizedCallback = std::move(callback);
|
||||
}
|
||||
virtual bool takeToThirdSection() {
|
||||
return false;
|
||||
}
|
||||
virtual bool showSectionInternal(
|
||||
not_null<::Window::SectionMemento*> memento,
|
||||
const ::Window::SectionShow ¶ms) {
|
||||
return false;
|
||||
}
|
||||
virtual bool closeByOutsideClick() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void closeLayer() {
|
||||
if (const auto callback = base::take(_closedCallback)) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
bool focusNextPrevChild(bool next) override;
|
||||
|
||||
virtual void doSetInnerFocus() {
|
||||
setFocus();
|
||||
}
|
||||
virtual void closeHook() {
|
||||
}
|
||||
|
||||
private:
|
||||
bool _closing = false;
|
||||
Fn<void()> _closedCallback;
|
||||
Fn<void()> _resizedCallback;
|
||||
|
||||
};
|
||||
|
||||
class LayerStackWidget : public RpWidget {
|
||||
public:
|
||||
LayerStackWidget(QWidget *parent, ShowFactory showFactory);
|
||||
|
||||
void finishAnimating();
|
||||
rpl::producer<> hideFinishEvents() const;
|
||||
|
||||
void setStyleOverrides(
|
||||
const style::Box *boxSt,
|
||||
const style::Box *layerSt);
|
||||
[[nodiscard]] const style::Box *boxStyleOverrideLayer() const {
|
||||
return _layerSt;
|
||||
}
|
||||
[[nodiscard]] const style::Box *boxStyleOverride() const {
|
||||
return _boxSt;
|
||||
}
|
||||
[[nodiscard]] ShowFactory showFactory() const {
|
||||
return _showFactory;
|
||||
}
|
||||
|
||||
void showBox(
|
||||
object_ptr<BoxContent> box,
|
||||
LayerOptions options,
|
||||
anim::type animated);
|
||||
void showLayer(
|
||||
std::unique_ptr<LayerWidget> layer,
|
||||
LayerOptions options,
|
||||
anim::type animated);
|
||||
void showSpecialLayer(
|
||||
object_ptr<LayerWidget> layer,
|
||||
anim::type animated);
|
||||
void showMainMenu(
|
||||
object_ptr<LayerWidget> menu,
|
||||
anim::type animated);
|
||||
bool takeToThirdSection();
|
||||
|
||||
bool canSetFocus() const;
|
||||
void setInnerFocus();
|
||||
|
||||
bool contentOverlapped(const QRect &globalRect);
|
||||
|
||||
void hideSpecialLayer(anim::type animated);
|
||||
void hideLayers(anim::type animated);
|
||||
void hideAll(anim::type animated);
|
||||
void hideTopLayer(anim::type animated);
|
||||
void setHideByBackgroundClick(bool hide);
|
||||
void removeBodyCache();
|
||||
|
||||
// If you need to divide animated hideAll().
|
||||
void hideAllAnimatedPrepare();
|
||||
void hideAllAnimatedRun();
|
||||
|
||||
bool showSectionInternal(
|
||||
not_null<::Window::SectionMemento*> memento,
|
||||
const ::Window::SectionShow ¶ms);
|
||||
|
||||
bool layerShown() const;
|
||||
const LayerWidget *topShownLayer() const;
|
||||
|
||||
~LayerStackWidget();
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private:
|
||||
void appendLayer(
|
||||
std::unique_ptr<LayerWidget> layer,
|
||||
anim::type animated);
|
||||
void prependLayer(
|
||||
std::unique_ptr<LayerWidget> layer,
|
||||
anim::type animated);
|
||||
void replaceLayer(
|
||||
std::unique_ptr<LayerWidget> layer,
|
||||
anim::type animated);
|
||||
void backgroundClicked();
|
||||
|
||||
LayerWidget *pushLayer(
|
||||
std::unique_ptr<LayerWidget> layer,
|
||||
anim::type animated);
|
||||
void showFinished();
|
||||
void hideCurrent(anim::type animated);
|
||||
void closeLayer(not_null<LayerWidget*> layer);
|
||||
|
||||
enum class Action {
|
||||
ShowMainMenu,
|
||||
ShowSpecialLayer,
|
||||
ShowLayer,
|
||||
HideSpecialLayer,
|
||||
HideLayer,
|
||||
HideAll,
|
||||
};
|
||||
template <typename SetupNew, typename ClearOld>
|
||||
bool prepareAnimation(
|
||||
SetupNew &&setupNewWidgets,
|
||||
ClearOld &&clearOldWidgets,
|
||||
Action action,
|
||||
anim::type animated);
|
||||
template <typename SetupNew, typename ClearOld>
|
||||
void startAnimation(
|
||||
SetupNew &&setupNewWidgets,
|
||||
ClearOld &&clearOldWidgets,
|
||||
Action action,
|
||||
anim::type animated);
|
||||
|
||||
void prepareForAnimation();
|
||||
void animationDone();
|
||||
|
||||
void setCacheImages();
|
||||
void clearLayers();
|
||||
void clearSpecialLayer();
|
||||
void initChildLayer(LayerWidget *layer);
|
||||
void updateLayerBoxes();
|
||||
void fixOrder();
|
||||
void sendFakeMouseEvent();
|
||||
void clearClosingLayers();
|
||||
|
||||
LayerWidget *currentLayer() {
|
||||
return _layers.empty() ? nullptr : _layers.back().get();
|
||||
}
|
||||
const LayerWidget *currentLayer() const {
|
||||
return const_cast<LayerStackWidget*>(this)->currentLayer();
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<LayerWidget>> _layers;
|
||||
std::vector<std::unique_ptr<LayerWidget>> _closingLayers;
|
||||
|
||||
object_ptr<LayerWidget> _specialLayer = { nullptr };
|
||||
object_ptr<LayerWidget> _mainMenu = { nullptr };
|
||||
|
||||
class BackgroundWidget;
|
||||
object_ptr<BackgroundWidget> _background;
|
||||
|
||||
ShowFactory _showFactory;
|
||||
|
||||
const style::Box *_boxSt = nullptr;
|
||||
const style::Box *_layerSt = nullptr;
|
||||
bool _hideByBackgroundClick = true;
|
||||
|
||||
rpl::event_stream<> _hideFinishStream;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
||||
235
Telegram/lib_ui/ui/layers/layers.style
Normal file
235
Telegram/lib_ui/ui/layers/layers.style
Normal file
@@ -0,0 +1,235 @@
|
||||
// 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
|
||||
//
|
||||
using "ui/basic.style";
|
||||
|
||||
using "ui/widgets/widgets.style";
|
||||
|
||||
ServiceCheck {
|
||||
margin: margins;
|
||||
diameter: pixels;
|
||||
shift: pixels;
|
||||
thickness: pixels;
|
||||
tip: point;
|
||||
small: pixels;
|
||||
large: pixels;
|
||||
stroke: pixels;
|
||||
color: color;
|
||||
duration: int;
|
||||
}
|
||||
|
||||
Box {
|
||||
buttonPadding: margins;
|
||||
buttonHeight: pixels;
|
||||
buttonWide: bool;
|
||||
button: RoundButton;
|
||||
margin: margins;
|
||||
title: FlatLabel;
|
||||
bg: color;
|
||||
titleAdditionalFg: color;
|
||||
shadowIgnoreTopSkip: bool;
|
||||
shadowIgnoreBottomSkip: bool;
|
||||
}
|
||||
|
||||
boxDuration: 200;
|
||||
boxRadius: 8px;
|
||||
|
||||
boxButtonFont: font(boxFontSize semibold);
|
||||
defaultBoxButtonTextStyle: TextStyle(semiboldTextStyle) {
|
||||
font: font(14px semibold);
|
||||
}
|
||||
defaultBoxButton: RoundButton(defaultLightButton) {
|
||||
width: -30px;
|
||||
height: 34px;
|
||||
textTop: 7px;
|
||||
style: defaultBoxButtonTextStyle;
|
||||
}
|
||||
|
||||
boxLabelStyle: TextStyle(boxTextStyle) {
|
||||
lineHeight: 22px;
|
||||
}
|
||||
|
||||
attentionBoxButton: RoundButton(defaultBoxButton) {
|
||||
textFg: attentionButtonFg;
|
||||
textFgOver: attentionButtonFgOver;
|
||||
textBgOver: attentionButtonBgOver;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: attentionButtonBgRipple;
|
||||
}
|
||||
}
|
||||
|
||||
defaultBoxCheckbox: Checkbox(defaultCheckbox) {
|
||||
width: -46px;
|
||||
textPosition: point(12px, 1px);
|
||||
style: boxTextStyle;
|
||||
}
|
||||
|
||||
boxRoundShadow: roundShadowRadius8px;
|
||||
|
||||
boxTitleFont: font(16px semibold);
|
||||
boxTitle: FlatLabel(defaultFlatLabel) {
|
||||
textFg: boxTitleFg;
|
||||
maxHeight: 24px;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: boxTitleFont;
|
||||
}
|
||||
}
|
||||
boxTitlePosition: point(24px, 13px);
|
||||
boxTitleHeight: 48px;
|
||||
boxTitleAdditionalSkip: 9px;
|
||||
boxTitleAdditionalFont: normalFont;
|
||||
boxScroll: defaultSolidScroll;
|
||||
|
||||
boxRowPadding: margins(24px, 0px, 24px, 0px);
|
||||
|
||||
boxTopMargin: 8px;
|
||||
|
||||
boxTitleCloseIcon: icon {{ "box_button_close", boxTitleCloseFg }};
|
||||
boxTitleCloseIconOver: icon {{ "box_button_close", boxTitleCloseFgOver }};
|
||||
boxTitleClose: IconButton(defaultIconButton) {
|
||||
width: boxTitleHeight;
|
||||
height: boxTitleHeight;
|
||||
|
||||
icon: boxTitleCloseIcon;
|
||||
iconOver: boxTitleCloseIconOver;
|
||||
|
||||
rippleAreaPosition: point(4px, 4px);
|
||||
rippleAreaSize: 40px;
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
boxTitleMenu: IconButton(boxTitleClose) {
|
||||
icon: icon {{ "title_menu_dots", boxTitleCloseFg }};
|
||||
iconOver: icon {{ "title_menu_dots", boxTitleCloseFgOver }};
|
||||
}
|
||||
|
||||
boxLinkButton: LinkButton(defaultLinkButton) {
|
||||
font: boxTextFont;
|
||||
overFont: font(boxFontSize underline);
|
||||
}
|
||||
|
||||
boxOptionListPadding: margins(0px, 0px, 0px, 0px);
|
||||
boxOptionListSkip: 20px;
|
||||
|
||||
boxWidth: 320px;
|
||||
boxWideWidth: 364px;
|
||||
boxPadding: margins(24px, 14px, 24px, 8px);
|
||||
boxMaxListHeight: 492px;
|
||||
boxLittleSkip: 10px;
|
||||
boxMediumSkip: 20px;
|
||||
|
||||
defaultBox: Box {
|
||||
buttonPadding: margins(6px, 10px, 10px, 10px);
|
||||
buttonHeight: 34px;
|
||||
button: defaultBoxButton;
|
||||
margin: margins(0px, 10px, 0px, 10px);
|
||||
bg: boxBg;
|
||||
title: boxTitle;
|
||||
titleAdditionalFg: boxTitleAdditionalFg;
|
||||
}
|
||||
layerBox: Box(defaultBox) {
|
||||
}
|
||||
boxLabel: FlatLabel(defaultFlatLabel) {
|
||||
// Keep minWidth <= boxWidth - boxPadding.left - boxPadding.right.
|
||||
minWidth: 256px;
|
||||
align: align(topleft);
|
||||
style: boxLabelStyle;
|
||||
}
|
||||
|
||||
boxLoadingAnimation: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
|
||||
color: windowSubTextFg;
|
||||
thickness: 2px;
|
||||
}
|
||||
boxLoadingSize: 20px;
|
||||
|
||||
defaultSubsectionTitle: FlatLabel(defaultFlatLabel) {
|
||||
style: TextStyle(semiboldTextStyle) {
|
||||
font: font(boxFontSize semibold);
|
||||
}
|
||||
textFg: windowActiveTextFg;
|
||||
minWidth: 240px;
|
||||
}
|
||||
defaultSubsectionTitlePadding: margins(22px, 7px, 10px, 9px);
|
||||
|
||||
separatePanelBorderCacheSize: 60px;
|
||||
separatePanelTitleHeight: 62px;
|
||||
separatePanelNoTitleHeight: 32px;
|
||||
separatePanelTitleBadgeSkip: 6px;
|
||||
separatePanelClose: IconButton(boxTitleClose) {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
|
||||
rippleAreaPosition: point(8px, 8px);
|
||||
rippleAreaSize: 44px;
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
separatePanelMenu: IconButton(separatePanelClose) {
|
||||
width: 44px;
|
||||
|
||||
icon: icon {{ "title_menu_dots", boxTitleCloseFg }};
|
||||
iconOver: icon {{ "title_menu_dots", boxTitleCloseFgOver }};
|
||||
|
||||
rippleAreaPosition: point(0px, 8px);
|
||||
}
|
||||
separatePanelMenuPosition: point(0px, 52px);
|
||||
separatePanelTitleFont: font(18px semibold);
|
||||
separatePanelTitle: FlatLabel(defaultFlatLabel) {
|
||||
textFg: boxTitleFg;
|
||||
maxHeight: 26px;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: separatePanelTitleFont;
|
||||
}
|
||||
}
|
||||
separatePanelTitleTop: 18px;
|
||||
separatePanelTitleLeft: 22px;
|
||||
separatePanelTitleSkip: 0px;
|
||||
separatePanelTitleBadgeTop: 4px;
|
||||
separatePanelSearch: IconButton(separatePanelClose) {
|
||||
width: 44px;
|
||||
|
||||
icon: icon {{ "box_search", boxTitleCloseFg }};
|
||||
iconOver: icon {{ "box_search", boxTitleCloseFgOver }};
|
||||
|
||||
rippleAreaPosition: point(0px, 8px);
|
||||
}
|
||||
separatePanelBack: IconButton(separatePanelClose) {
|
||||
icon: icon {{ "box_button_back", boxTitleCloseFg }};
|
||||
iconOver: icon {{ "box_button_back", boxTitleCloseFgOver }};
|
||||
}
|
||||
separatePanelDuration: 150;
|
||||
|
||||
fullScreenPanelClose: IconButton {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
|
||||
icon: icon {{ "box_button_close", radialFg }};
|
||||
iconOver: icon {{ "box_button_close", radialFg }};
|
||||
iconPosition: point(-1px, -1px);
|
||||
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
rippleAreaSize: 44px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: shadowFg;
|
||||
}
|
||||
}
|
||||
fullScreenPanelBack: IconButton(fullScreenPanelClose) {
|
||||
icon: icon {{ "box_button_back", radialFg }};
|
||||
iconOver: icon {{ "box_button_back", radialFg }};
|
||||
}
|
||||
fullScreenPanelMenu: IconButton(fullScreenPanelClose) {
|
||||
icon: icon {{ "title_menu_dots", radialFg }};
|
||||
iconOver: icon {{ "title_menu_dots", radialFg }};
|
||||
}
|
||||
|
||||
webviewDialogButton: defaultBoxButton;
|
||||
webviewDialogDestructiveButton: attentionBoxButton;
|
||||
webviewDialogSubmit: RoundButton(defaultActiveButton) {
|
||||
width: -48px;
|
||||
height: 34px;
|
||||
textTop: 7px;
|
||||
style: defaultBoxButtonTextStyle;
|
||||
}
|
||||
webviewDialogPadding: margins(8px, 12px, 15px, 12px);
|
||||
61
Telegram/lib_ui/ui/layers/show.cpp
Normal file
61
Telegram/lib_ui/ui/layers/show.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
// 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/show.h"
|
||||
|
||||
#include "ui/toast/toast.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
using namespace Toast;
|
||||
|
||||
} // namespace
|
||||
|
||||
void Show::showBox(
|
||||
object_ptr<BoxContent> content,
|
||||
LayerOptions options,
|
||||
anim::type animated) const {
|
||||
return showOrHideBoxOrLayer(std::move(content), options, animated);
|
||||
}
|
||||
|
||||
void Show::showLayer(
|
||||
std::unique_ptr<LayerWidget> layer,
|
||||
LayerOptions options,
|
||||
anim::type animated) const {
|
||||
return showOrHideBoxOrLayer(std::move(layer), options, animated);
|
||||
}
|
||||
|
||||
void Show::hideLayer(anim::type animated) const {
|
||||
return showOrHideBoxOrLayer(v::null, LayerOptions(), animated);
|
||||
}
|
||||
|
||||
base::weak_ptr<Instance> Show::showToast(Config &&config) const {
|
||||
if (const auto strong = _lastToast.get()) {
|
||||
strong->hideAnimated();
|
||||
}
|
||||
_lastToast = valid()
|
||||
? Toast::Show(toastParent(), std::move(config))
|
||||
: base::weak_ptr<Instance>();
|
||||
return _lastToast;
|
||||
}
|
||||
|
||||
base::weak_ptr<Instance> Show::showToast(
|
||||
TextWithEntities &&text,
|
||||
crl::time duration) const {
|
||||
return showToast({ .text = std::move(text), .duration = duration });
|
||||
}
|
||||
|
||||
base::weak_ptr<Instance> Show::showToast(
|
||||
const QString &text,
|
||||
crl::time duration) const {
|
||||
return showToast({
|
||||
.text = TextWithEntities{ text },
|
||||
.duration = duration,
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
81
Telegram/lib_ui/ui/layers/show.h
Normal file
81
Telegram/lib_ui/ui/layers/show.h
Normal file
@@ -0,0 +1,81 @@
|
||||
// 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
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/weak_ptr.h"
|
||||
#include "ui/layers/layer_widget.h"
|
||||
|
||||
struct TextWithEntities;
|
||||
|
||||
namespace anim {
|
||||
enum class type : uchar;
|
||||
} // namespace anim
|
||||
|
||||
namespace Ui::Toast {
|
||||
struct Config;
|
||||
class Instance;
|
||||
} // namespace Ui::Toast
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class BoxContent;
|
||||
class LayerWidget;
|
||||
|
||||
inline constexpr auto kZOrderBasic = 0;
|
||||
|
||||
class Show {
|
||||
public:
|
||||
virtual ~Show() = 0;
|
||||
virtual void showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<BoxContent>,
|
||||
std::unique_ptr<LayerWidget>> &&layer,
|
||||
LayerOptions options,
|
||||
anim::type animated) const = 0;
|
||||
[[nodiscard]] virtual not_null<QWidget*> toastParent() const = 0;
|
||||
[[nodiscard]] virtual bool valid() const = 0;
|
||||
virtual operator bool() const = 0;
|
||||
|
||||
void showBox(
|
||||
object_ptr<BoxContent> content,
|
||||
LayerOptions options = LayerOption::KeepOther,
|
||||
anim::type animated = anim::type()) const;
|
||||
void showLayer(
|
||||
std::unique_ptr<LayerWidget> layer,
|
||||
LayerOptions options = LayerOption::KeepOther,
|
||||
anim::type animated = anim::type()) const;
|
||||
void hideLayer(anim::type animated = anim::type()) const;
|
||||
|
||||
base::weak_ptr<Toast::Instance> showToast(Toast::Config &&config) const;
|
||||
base::weak_ptr<Toast::Instance> showToast(
|
||||
TextWithEntities &&text,
|
||||
crl::time duration = 0) const;
|
||||
base::weak_ptr<Toast::Instance> showToast(
|
||||
const QString &text,
|
||||
crl::time duration = 0) const;
|
||||
|
||||
template <
|
||||
typename BoxType,
|
||||
typename = std::enable_if_t<std::is_base_of_v<BoxContent, BoxType>>>
|
||||
base::weak_qptr<BoxType> show(
|
||||
object_ptr<BoxType> content,
|
||||
LayerOptions options = LayerOption::KeepOther,
|
||||
anim::type animated = anim::type()) const {
|
||||
auto result = base::weak_qptr<BoxType>(content.data());
|
||||
showBox(std::move(content), options, animated);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable base::weak_ptr<Toast::Instance> _lastToast;
|
||||
|
||||
};
|
||||
|
||||
inline Show::~Show() = default;
|
||||
|
||||
} // namespace Ui
|
||||
Reference in New Issue
Block a user